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

j5fpnvbx  于 2023-03-25  发布在  其他
关注(0)|答案(2)|浏览(124)

我试图创建一个对象,它存储从static函数创建的对象的元组,但是当创建tuple时,对象被复制到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

cvxl0en2

cvxl0en21#

struct nocopy{
  nocopy(){}
  nocopy(nocopy const&)=delete;
  nocopy(nocopy&&)=delete;
};
struct maker{
  operator nocopy()const{return {};}
};
std::tuple<nocopy,nocopy> t( maker{}, maker{} );

这将绕过c++17或更高版本中的复制构造函数。
类似的技术可用于获取返回对象工作的任意函数。
诀窍在于,没有接受maker对象的构造函数,那么C+福尔斯在maker上调用转换运算符,其返回值可以直接省略到对象中。
在17之前,这可能会在运行时绕过复制ctor,但除非存在复制(或移动),否则不会编译(即使复制/移动ctor有副作用)。在那之前是可选的。

lmyy7pcs

lmyy7pcs2#

对于静态函数,它不能工作,因为通过std::tuple的构造函数参数进行省略是不可能的。
不幸的是,std::tuple缺少一个std::piecewise_construct构造函数,就像std::pair一样,它可以用来在元组/对的构造函数中就地执行construct_plugin的操作。
您可以编写自己的元组实现来支持它,或者您可以将构造函数重载添加到您的插件中,以支持以当前静态帮助函数的方式从元组进行构造,或者您可以使用另一个答案中描述的转换函数技巧。
之所以会执行 copy 而不是move,只是因为你声明了析构函数和copy构造函数。如果你没有声明,那么move就会被使用。定义copy/move构造函数并不能帮助你避免调用它们。无论插件类是如何定义的,省略都是不可能的。
另外,你可能想要std::forward_as_tuple而不是std::make_tuple,因为后者在传递给插件构造函数之前也会复制单个对象,我不认为这是你想要的(这对你传递的内置类型来说并不重要)。

相关问题