std :: tuple用于不可复制和不可移动的对象

时间:2015-09-24 13:47:26

标签: c++ c++11 tuples

我有一个带有副本的班级移动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。那么,被毁物体的元组有什么意义呢?

2 个答案:

答案 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) >= 1is_constructible<Ti, Ui&&>::value适用于所有i ,否则此构造函数不得参与重载决策。构造函数是唯一的显式   如果至少有一个 i is_convertible<Ui&&, Ti>::valuefalse

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});