std::tuple 对于不可复制和不可移动的对象
std::tuple for non-copyable and non-movable object
我有一个 class 删除了复制和移动 ctor。
struct A
{
A(int a):data(a){}
~A(){ std::cout << "~A()" << this << " : " << data << std::endl; }
A(A const &obj) = delete;
A(A &&obj) = delete;
friend std::ostream & operator << ( std::ostream & out , A const & obj);
int data;
};
我想用这个 class 的对象创建一个元组。但以下不编译:
auto p = std::tuple<A,A>(A{10},A{20});
另一方面,以下 确实 编译,但给出了令人惊讶的输出。
int main() {
auto q = std::tuple<A&&,A&&>(A{100},A{200});
std::cout << "q created\n";
}
输出
~A()0x22fe10 : 100
~A()0x22fe30 : 200
q created
这意味着一旦元组构造行结束,就会调用对象的dtor。那么,销毁对象元组的意义是什么?
这很糟糕:
auto q = std::tuple<A&&,A&&>(A{100},A{200});
您正在构造一个 tuple
对在表达式末尾被破坏的临时对象的右值引用,因此您留下了悬空引用。
正确的说法是:
std::tuple<A, A> q(100, 200);
但是,直到最近,标准才支持上述内容。在 N4296 中,tuple
的相关构造函数周围的措辞是 [tuple.cnstr]:
template <class... UTypes>
constexpr explicit tuple(UTypes&&... u);
Requires: sizeof...(Types) == sizeof...(UTypes)
. is_constructible<Ti, Ui&&>::value
is true
for all i
.
Effects: Initializes the elements in the tuple with the corresponding value in std::forward<UTypes>(u)
.
Remark: This constructor shall not participate in overload resolution unless each type in UTypes
is
implicitly convertible to its corresponding type in Types
.
因此,此构造函数未参与重载决策,因为 int
无法隐式转换为 A
。这已通过采用 Improving pair
and tuple
得到解决,它恰好解决了您的用例:
struct D { D(int); D(const D&) = delete; };
std::tuple<D> td(12); // Error
此构造函数的新措辞来自 N4527:
Remarks: This constructor shall not participate in overload resolution unless sizeof...(Types) >= 1
and is_constructible<Ti, Ui&&>::value
is true for all i
. The constructor is explicit if and only
if is_convertible<Ui&&, Ti>::value
is false
for at least one i.
并且is_constructible<A, int&&>::value
是正确的。
为了以另一种方式展示差异,这里是一个极其精简的元组实现:
struct D { D(int ) {} D(const D& ) = delete; };
template <typename T>
struct Tuple {
Tuple(const T& t)
: T(t)
{ }
template <typename U,
#ifdef USE_OLD_RULES
typename = std::enable_if_t<std::is_convertible<U, T>::value>
#else
typename = std::enable_if_t<std::is_constructible<T, U&&>::value>
#endif
>
Tuple(U&& u)
: t(std::forward<U>(u))
{ }
T t;
};
int main()
{
Tuple<D> t(12);
}
如果定义了 USE_OLD_RULES
,第一个构造函数是唯一可行的构造函数,因此代码将无法编译,因为 D
是不可复制的。否则,第二个构造函数是最可行的候选者,并且是良构的。
采用的时间足够新,gcc 5.2 和 clang 3.6 实际上都不会编译这个示例。所以你要么需要一个比它更新的编译器(gcc 6.0 可以工作),要么想出一个不同的设计。
你的问题是你明确要求一个右值引用的元组,而右值引用离指针不远。
因此 auto q = std::tuple<A&&,A&&>(A{100},A{200});
创建两个 A 对象,获取对它们的(右值)引用,使用引用构建元组...并销毁临时对象,留下两个悬空引用
即使据说它比旧的 C 及其悬挂指针更安全,C++ 仍然允许程序员编写错误的程序。
无论如何,以下是有意义的(注意 A& 而不是 A&& 的用法):
int main() {
A a(100), b(100); // Ok, a and b will leave as long as main
auto q = tuple<A&, A&>(a, b); // ok, q contains references to a and b
...
return 0; // Ok, q, a and b will be destroyed
}
我有一个 class 删除了复制和移动 ctor。
struct A
{
A(int a):data(a){}
~A(){ std::cout << "~A()" << this << " : " << data << std::endl; }
A(A const &obj) = delete;
A(A &&obj) = delete;
friend std::ostream & operator << ( std::ostream & out , A const & obj);
int data;
};
我想用这个 class 的对象创建一个元组。但以下不编译:
auto p = std::tuple<A,A>(A{10},A{20});
另一方面,以下 确实 编译,但给出了令人惊讶的输出。
int main() {
auto q = std::tuple<A&&,A&&>(A{100},A{200});
std::cout << "q created\n";
}
输出
~A()0x22fe10 : 100
~A()0x22fe30 : 200
q created
这意味着一旦元组构造行结束,就会调用对象的dtor。那么,销毁对象元组的意义是什么?
这很糟糕:
auto q = std::tuple<A&&,A&&>(A{100},A{200});
您正在构造一个 tuple
对在表达式末尾被破坏的临时对象的右值引用,因此您留下了悬空引用。
正确的说法是:
std::tuple<A, A> q(100, 200);
但是,直到最近,标准才支持上述内容。在 N4296 中,tuple
的相关构造函数周围的措辞是 [tuple.cnstr]:
template <class... UTypes> constexpr explicit tuple(UTypes&&... u);
Requires:
sizeof...(Types) == sizeof...(UTypes)
.is_constructible<Ti, Ui&&>::value
is true for alli
.
Effects: Initializes the elements in the tuple with the corresponding value instd::forward<UTypes>(u)
.
Remark: This constructor shall not participate in overload resolution unless each type inUTypes
is implicitly convertible to its corresponding type inTypes
.
因此,此构造函数未参与重载决策,因为 int
无法隐式转换为 A
。这已通过采用 Improving pair
and tuple
得到解决,它恰好解决了您的用例:
struct D { D(int); D(const D&) = delete; };
std::tuple<D> td(12); // Error
此构造函数的新措辞来自 N4527:
Remarks: This constructor shall not participate in overload resolution unless
sizeof...(Types) >= 1
andis_constructible<Ti, Ui&&>::value
is true for alli
. The constructor is explicit if and only ifis_convertible<Ui&&, Ti>::value
isfalse
for at least one i.
并且is_constructible<A, int&&>::value
是正确的。
为了以另一种方式展示差异,这里是一个极其精简的元组实现:
struct D { D(int ) {} D(const D& ) = delete; };
template <typename T>
struct Tuple {
Tuple(const T& t)
: T(t)
{ }
template <typename U,
#ifdef USE_OLD_RULES
typename = std::enable_if_t<std::is_convertible<U, T>::value>
#else
typename = std::enable_if_t<std::is_constructible<T, U&&>::value>
#endif
>
Tuple(U&& u)
: t(std::forward<U>(u))
{ }
T t;
};
int main()
{
Tuple<D> t(12);
}
如果定义了 USE_OLD_RULES
,第一个构造函数是唯一可行的构造函数,因此代码将无法编译,因为 D
是不可复制的。否则,第二个构造函数是最可行的候选者,并且是良构的。
采用的时间足够新,gcc 5.2 和 clang 3.6 实际上都不会编译这个示例。所以你要么需要一个比它更新的编译器(gcc 6.0 可以工作),要么想出一个不同的设计。
你的问题是你明确要求一个右值引用的元组,而右值引用离指针不远。
因此 auto q = std::tuple<A&&,A&&>(A{100},A{200});
创建两个 A 对象,获取对它们的(右值)引用,使用引用构建元组...并销毁临时对象,留下两个悬空引用
即使据说它比旧的 C 及其悬挂指针更安全,C++ 仍然允许程序员编写错误的程序。
无论如何,以下是有意义的(注意 A& 而不是 A&& 的用法):
int main() {
A a(100), b(100); // Ok, a and b will leave as long as main
auto q = tuple<A&, A&>(a, b); // ok, q contains references to a and b
...
return 0; // Ok, q, a and b will be destroyed
}