首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >在使用std::make_tuple时,如何避免构造函数的未定义执行顺序

在使用std::make_tuple时,如何避免构造函数的未定义执行顺序
EN

Stack Overflow用户
提问于 2012-12-27 22:10:15
回答 5查看 1.8K关注 0票数 19

如果构造函数的执行顺序很重要,我如何使用std::make_tuple?

例如,我猜A类的构造函数和B类的构造函数的执行顺序是未定义的:

代码语言:javascript
复制
std::tuple<A, B> t(std::make_tuple(A(std::cin), B(std::cin)));

我是在读完对这个问题的评论后得出这个结论的。

Translating a std::tuple into a template parameter pack

那就是说这个

代码语言:javascript
复制
template<typename... args>
std::tuple<args...> parse(std::istream &stream) {
  return std::make_tuple(args(stream)...);
}

实现具有未定义的构造函数的执行顺序。

提供一些上下文的更新:

为了给我正在尝试做的事情提供更多的背景,这里有一个草图:

我想在二进制解析/序列化的帮助下从标准输入中读入一些序列化的对象。下面是一个如何进行这种解析和序列化的示例:example/cxx/tree/binary/xdr/driver.cxx

代码语言:javascript
复制
xml_schema::istream<XDR> ixdr (xdr); 
std::auto_ptr<catalog> copy (new catalog (ixdr));

我希望能够指定序列化对象所具有的类的列表(例如,catalog、catalog、3个序列化对象的someOtherSerializableClass ),并将该信息存储为类型定义

代码语言:javascript
复制
template <typename... Args>
struct variadic_typedef {};

typedef variadic_typedef<catalog, catalog, someOtherSerializableClass> myTypes;

按照Is it possible to “store” a template parameter pack without expanding it?中的建议

并找到一种方法来获得一个std::tuple,以便在解析完成后使用。一个素描:

代码语言:javascript
复制
auto serializedObjects(binaryParse<myTypes>(std::cin));

其中serializedObjects的类型为

代码语言:javascript
复制
std::tuple<catalog, catalog, someOtherSerializableClass>
EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2012-12-27 23:28:52

简单的解决方案不是首先使用std::make_tuple(...),而是直接构造std::tuple<...>:调用成员的构造函数的顺序是定义良好的:

代码语言:javascript
复制
template <typename>
std::istream& dummy(std::istream& in) {
    return in;
}
template <typename... T>
std::tuple<T...> parse(std::istream& in) {
    return std::tuple<T...>(dummy<T>(in)...);
}

函数模板dummy<T>()仅用于扩展某些内容。该顺序由std::tuple<T...>中元素的构造顺序决定

代码语言:javascript
复制
template <typename... T>
    template <typename... U>
    std::tuple<T...>::tuple(U...&& arg)
        : members_(std::forward<U>(arg)...) { // NOTE: pseudo code - the real code is
    }                                        //       somewhat more complex

根据下面的讨论和Xeo的评论,似乎更好的替代方案是使用

代码语言:javascript
复制
template <typename... T>
std::tuple<T...> parse(std::istream& in) {
    return std::tuple<T...>{ T(in)... };
}

使用大括号初始化是可行的,因为大括号初始化器列表中参数的求值顺序就是它们出现的顺序。T{...}的语义在12.6.1 class.explicit.init第2段中进行了描述,声明它遵循列表初始化语义的规则(注意:这与std::initializer_list无关,它只适用于同构类型)。排序约束在8.5.4 dcl.init.list第4段中。

票数 10
EN

Stack Overflow用户

发布于 2012-12-27 23:43:10

正如评论所说,您可以只使用initializer-list:

代码语言:javascript
复制
return std::tuple<args...>{args(stream)...};

这将适用于std::tuple和诸如此类的(支持初始化器列表)。

但我得到了另一个更通用的解决方案,在不能使用initializer-list的情况下很有用。所以让我们不使用initializer-list来解决这个问题:

代码语言:javascript
复制
template<typename... args>
std::tuple<args...> parse(std::istream &stream) {
  return std::make_tuple(args(stream)...);
}

在我解释我的解决方案之前,我想先讨论一下这个问题。事实上,一步一步地思考这个问题也会帮助我们最终找到解决方案。因此,为了简化讨论(和思考过程),让我们假设args扩展到3种不同的类型,即。XYZ,即args = {X, Y, Z},然后我们可以沿着这些思路思考,一步一步地找到解决方案:

首先也是最重要的是,XYZ的构造函数可以以任何顺序执行,因为我们希望X首先构造YZ的C++ and并没有指定计算函数参数的顺序。或者至少我们想要模拟这种行为,这意味着X必须用输入流开头的数据构造(假设数据是xData),Y必须用紧跟在xData之后的数据构造,依此类推。

  • 正如我们所知,X不能保证首先构造,所以我们需要假装。基本上,我们将从流中读取数据,就好像它在流的开头一样,即使首先构造了Z,这似乎也是不可能的。只要我们从输入流中读取数据,这是不可能的,但是我们从一些可索引的数据结构中读取数据,比如std::vector,那么这是可能的。

  • 所以我的解决方案这样做:它将首先填充一个std::vector,然后所有参数都将从这个向量中读取数据。

  • 我的解决方案假设流中的每一行都包含构造任何类型的对象所需的所有数据。

代码:

代码语言:javascript
复制
//PARSE FUNCTION 
template<typename... args>
std::tuple<args...> parse(std::istream &stream) 
{
  const int N = sizeof...(args);
  return tuple_maker<args...>().make(stream, typename genseq<N>::type() );
}

并且tuple_maker被定义为:

代码语言:javascript
复制
//FRAMEWORK - HELPER ETC

template<int ...>
struct seq {};

template<int M, int ...N>
struct genseq  : genseq<M-1,M-1, N...> {};

template<int ...N>
struct genseq<0,N...>
{
   typedef seq<N...> type;
};

template<typename...args>
struct tuple_maker
{
   template<int ...N>
   std::tuple<args...> make(std::istream & stream, const seq<N...> &)
   {
     return std::make_tuple(args(read_arg<N>(stream))...);
   }
   std::vector<std::string> m_params;
   std::vector<std::unique_ptr<std::stringstream>> m_streams;
   template<int Index>
   std::stringstream & read_arg(std::istream & stream) 
   {
     if ( m_params.empty() )
     {
        std::string line;
        while ( std::getline(stream, line) ) //read all at once!
        {
                m_params.push_back(line);
        }
     }
     auto pstream = new std::stringstream(m_params.at(Index));
     m_streams.push_back(std::unique_ptr<std::stringstream>(pstream));
     return *pstream;
   }
};

测试代码

代码语言:javascript
复制
///TEST CODE

template<int N>
struct A 
{
    std::string data;
    A(std::istream & stream) 
    {
        stream >> data;
    }
    friend std::ostream& operator << (std::ostream & out, A<N> const & a)
    {
        return out << "A" << N << "::data = " << a.data ;
    }
};

//three distinct classes!
typedef A<1> A1; 
typedef A<2> A2;
typedef A<3> A3;

int main()
{
    std::stringstream ss("A1\nA2\nA3\n");
    auto tuple = parse<A1,A2,A3>(ss);
    std::cout << std::get<0>(tuple) << std::endl;
    std::cout << std::get<1>(tuple) << std::endl;
    std::cout << std::get<2>(tuple) << std::endl;
}

输出:

代码语言:javascript
复制
A1::data = A1
A2::data = A2
A3::data = A3

这是意料之中的。看看你自己的demo at ideone。:-)

请注意,此解决方案通过读取第一次调用read_arg本身中的所有行,以及所有后续调用使用索引从std::vector读取,从而避免了从流中读取顺序的问题。

现在,您可以将一些printf放入类的构造函数中,只是为了查看构造的顺序与parse函数模板的模板参数的顺序相同,这很有趣。此外,这里使用的技术对于不能使用列表初始化的地方也很有用。

票数 3
EN

Stack Overflow用户

发布于 2012-12-27 22:27:05

make_tuple在这里没有什么特别之处。C++中的任何函数调用都允许以未指定的顺序调用其参数(允许编译器自由优化)。

我真的不建议使用有副作用的构造函数,因为顺序很重要(这将是维护的噩梦),但是如果你绝对需要这样做,你总是可以显式地构造对象来设置你想要的顺序:

代码语言:javascript
复制
A a(std::cin);
std::tuple<A, B> t(std::make_tuple(a, B(std::cin)));
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/14056000

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档