我有一个带有副本的班级移动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;
};
我想用这个类的对象创建一个元组。但以下内容无法编译:
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。那么,被毁物体的元组有什么意义呢?
答案 0 :(得分:11)
这很糟糕:
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);
需要:
sizeof...(Types) == sizeof...(UTypes)
。is_constructible<Ti, Ui&&>::value
是真的 适用于所有i
效果:使用std::forward<UTypes>(u)
中的相应值初始化元组中的元素。
备注:此构造函数不应参与重载决策,除非UTypes
中的每个类型都是 隐式转换为Types
中的相应类型。
因此,此构造函数未参与重载解析,因为int
无法隐式转换为A
。通过Improving pair
and tuple
已经解决了这个问题,它正好解决了您的用例:
struct D { D(int); D(const D&) = delete; };
std::tuple<D> td(12); // Error
此构造函数的新措辞来自N4527:
备注:除非
sizeof...(Types) >= 1
和is_constructible<Ti, Ui&&>::value
适用于所有i
,否则此构造函数不得参与重载决策。构造函数是唯一的显式 如果至少有一个 iis_convertible<Ui&&, Ti>::value
为false
。
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工作)或者想出一个不同的设计。
答案 1 :(得分:1)
你的问题是你明确要求一个rvalue引用元组,并且一个rvalue引用离指针不远。
所以<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"
monitoring="autodetect" dynamicConfig="true">
<diskStore path="java.io.tmpdir/ehcache" />
<defaultCache maxEntriesLocalHeap="10000" eternal="false"
timeToIdleSeconds="120" timeToLiveSeconds="120" diskSpoolBufferSizeMB="30"
maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU" statistics="true">
<persistence strategy="localTempSwap" />
</defaultCache>
<cache name="employee" maxEntriesLocalHeap="10000" eternal="false"
timeToIdleSeconds="5" timeToLiveSeconds="10">
<persistence strategy="localTempSwap" />
</cache>
<cache name="org.hibernate.cache.internal.StandardQueryCache"
maxEntriesLocalHeap="5" eternal="false" timeToLiveSeconds="120">
<persistence strategy="localTempSwap" />
</cache>
<cache name="org.hibernate.cache.spi.UpdateTimestampsCache"
maxEntriesLocalHeap="5000" eternal="true">
<persistence strategy="localTempSwap" />
</cache>
</ehcache>
创建两个A对象,对它们进行(rvalue)引用,用引用构建元组...并销毁临时对象,留下两个悬空引用
即使它被认为比好的旧C及其悬空指针更安全,C ++仍然允许程序员编写错误的程序。
无论如何,以下内容是有意义的(注意使用A&amp;而不是A&amp;&amp;):
auto q = std::tuple<A&&,A&&>(A{100},A{200});