在 C++ 中从返回值创建元组时避免额外复制

问题描述 投票:0回答:2

我正在尝试创建一个对象,该对象存储从

static
函数创建的对象元组,但是当创建
tuple
时,对象被复制到
tuple
而不是直接放入其中,调用
tuple
中的每个对象的析构函数两次,这是我想避免的行为。有没有办法解决这个问题而不必为每个插件类创建自定义移动/复制构造函数?

代码如下:

#include <tuple>
#include <iostream>

namespace details
{
    template<typename PluginT, typename ContainerT, typename TupleT, size_t... Is>
    static PluginT construct_plugin(ContainerT& container, TupleT&& tuple, std::index_sequence<Is...>) 
    {
        return PluginT(container, std::get<Is>(std::forward<TupleT>(tuple))...);
    }

    template<typename PluginT, typename ContainerT, typename TupleT>
    static PluginT construct_plugin(ContainerT& container, TupleT&& tuple) 
    {
        return construct_plugin<PluginT>(container, std::forward<TupleT>(tuple), std::make_index_sequence<std::tuple_size<std::decay_t<TupleT>>::value>{});
    }
}

struct simple_plugin
{
    template<typename ContainerT>
    simple_plugin(ContainerT& container) {}

    ~simple_plugin()
    {
        std::cout << "simple_plugin destructor" << std::endl;
    }
};

struct plugin_with_params
{
    template<typename ContainerT>
    plugin_with_params(ContainerT& container, int argc, char* argv[]) {}

    ~plugin_with_params()
    {
        std::cout << "plugin_with_params destructor" << std::endl;
    }
};

template<typename... PluginTs>
struct plugin_container
{
    std::tuple<PluginTs...> plugins;

    template<typename... TupleTs>
    plugin_container(TupleTs&&... tuples) :
        plugins(details::construct_plugin<PluginTs>(*this, std::forward<TupleTs>(tuples))...) {}
};


int main(int argc, char* argv[])
{
    plugin_container<simple_plugin, plugin_with_params> container(std::make_tuple(), std::make_tuple(argc, argv));
    return 0;
}

这是行动中的行为: https://godbolt.org/z/bqjv5r88x

c++ constructor tuples copy move
2个回答
5
投票
struct nocopy{
  nocopy(){}
  nocopy(nocopy const&)=delete;
  nocopy(nocopy&&)=delete;
};
struct maker{
  operator nocopy()const{return {};}
};
std::tuple<nocopy,nocopy> t( maker{}, maker{} );

这会绕过 或更高版本中的复制构造函数。

类似的技术可用于获取返回对象的任意函数。

诀窍在于没有接受

maker
对象的构造函数,然后C++调用
maker
上的转换运算符,其返回值可以直接省略到对象中。

在 17 之前,这可能会在运行时绕过复制构造函数,但除非复制(或移动)存在(即使复制/移动构造函数有副作用),否则不会编译。这是因为 17 添加了保证省略;在此之前它是可选的。


2
投票

对于静态函数,它无法工作,因为通过

std::tuple
的构造函数参数进行省略是不可能的。

不幸的是,

std::tuple
缺少像
std::piecewise_construct
那样的
std::pair
构造函数,它可以用来让你的
construct_plugin
的动作在元组/对的构造函数中就地执行。

您可以编写自己的元组实现来支持它,或者您可以将构造函数重载添加到支持元组构建的插件,就像当前静态辅助函数所做的那样,或者您可以使用另一个答案中描述的转换函数技巧.


完成copy而不是移动的原因仅仅是因为你声明了析构函数和复制构造函数。如果您声明 none 则将使用 moves 代替。定义复制/移动构造函数无法帮助您避免调用它们。无论插件类如何定义,省略都是不可能的。


此外,您可能想要

std::forward_as_tuple
而不是
std::make_tuple
,因为后者在传递给插件构造函数之前还会复制单个对象,我认为这不是您想要的(但这并不重要)对于您传递的内置类型)。

© www.soinside.com 2019 - 2024. All rights reserved.