【问题标题】:Can we return objects having a deleted/private copy/move constructor by value from a function?我们可以从函数中按值返回具有已删除/私有复制/移动构造函数的对象吗?
【发布时间】:2011-10-28 22:46:39
【问题描述】:

在 C++03 中,不可能按值返回具有私有未定义复制构造函数的类的对象:

struct A { A(int x) { ... } private: A(A const&); };

A f() {
  return A(10); // error!
  return 10;    // error too!
}

我想知道,在 C++11 中是否取消了这个限制,从而可以为没有用于复制或移动的构造函数的类编写具有类类型返回类型的函数?我记得允许函数的调用者使用新返回的对象可能很有用,但他们无法复制该值并将其存储在某处。

【问题讨论】:

  • “按值...不复制或移动”返回对象是什么意思?
  • @Richard 有一个不返回 void 或引用的函数定义。我的意思是一个返回一个新类对象的函数。我会澄清的。
  • @Xeo :因为这是一个更了解的人提出的愚蠢问题。我这么说是作为一个在我知道之前就已经知道/尊重 litb 的人。 ;-]
  • @ildjarn 哦,我对爱的感觉如何。我在下面发现了这个测验的解决方案!
  • @Johannes : 够公平的!

标签: c++ c++11 copy-constructor


【解决方案1】:

这是它的工作原理

A f() {
  return { 10 };
}

即使A 没有工作副本或移动构造函数,也没有其他可以复制或移动A 的构造函数,这仍然有效!

要利用 C++11 的这一特性,构造函数(在本例中为 int)必须是非显式的。

【讨论】:

  • 你和你的测验问题。 :(
  • 它不能用g++ -std=c++0x 编译。顺便问一下,你应该怎么称呼它?
  • 鉴于大卫的回答似乎表明这仍然应该是非法的,您能否也发布支持您回答的相关标准?
  • @ildjarn 哎呀,不知何故我错过了这些 cmets。这是在 8.5.4p3 中指定的。请注意,我们用{ 10 } 初始化A(表示返回值的对象)。没有复制,也没有移动。
  • C++17 的保证复制省略会影响这个吗?
【解决方案2】:

限制尚未解除。根据访问说明符,第 12.8/32 节中有一条说明:

无论是否会发生复制省略,都必须执行两阶段重载解决方案。它决定了在不执行省略的情况下要调用的构造函数,并且即使调用被省略,所选构造函数也必须是可访问的。

截至已删除的复制/移动构造函数 §8.4.3/2 指出

隐式或显式引用已删除函数的程序,而不是声明它,是格式错误的。 [注意:这包括隐式或显式调用函数并形成指向函数的指针或指向成员的指针。它甚至适用于未潜在评估的表达式中的引用。如果函数被重载,则仅当函数被重载决议选择时才会被引用。 ——尾注]

不确定这个特殊情况,但我对引用的理解是,如果在 §12.8/32 中的重载决议之后选择了删除的复制/移动构造函数,即使该操作被省略,也可能构成 reference 到函数,程序会不正确。

【讨论】:

    【解决方案3】:

    以上代码在 C++11 中仍然是病态的。但是您可以向A 添加一个公共移动构造函数,这样就合法了:

    struct A
    {
        A(int x) {}
        A(A&&);
    private:
        A(A const&);
    };
    
    A f() {
      return A(10); // Ok!
    }
    

    【讨论】:

    • 嗨!对不起,我应该在问题的正文中重复我在标题中的内容。现在会这样做。但无论哪种方式,这都是一个很好的评论!
    【解决方案4】:

    我想知道,这个限制在 C++11 中取消了吗?

    怎么可能?通过按值返回某些东西,按照定义,您就是在复制(或移动)它。虽然 C++ 可以允许在某些情况下省略复制/移动,但它仍然按照规范进行复制(或移动)。

    我记得允许函数的调用者使用返回的对象可能很有用,但他们无法复制该值并将其存储在某处。

    是的。您摆脱了复制构造函数/赋值,但允许值被移动std::unique_ptr 这样做。

    您可以按值返回unique_ptr。但是这样做,您将返回一个“prvalue”:一个正在被销毁的临时值。因此,如果您有这样的函数g

    std::unique_ptr<SomeType> g() {...}
    

    你可以这样做:

    std::unique_ptr<SomeType> value = g();
    

    但不是这个

    std::unique_ptr<SomeType> value1 = g();
    std::unique_ptr<SomeType> value2 = g();
    value1 = value 2;
    

    但这可能的:

    std::unique_ptr<SomeType> value = g();
    value = g();
    

    第二行调用value 上的移动赋值运算符。它将删除旧指针并将新指针移动到其中,将旧值留空。

    通过这种方式,您可以确保任何unique_ptr 的内容只存储在一个位置。您无法阻止它们在多个位置引用它(通过指向 unique_ptr 的指针或其他),但内存中最多有一个位置存储实际指针。

    同时删除复制和移动构造函数会创建一个 immobile 对象。它被创造的地方就是它的价值所在,永远。运动可以让您拥有独特的所有权,但不会静止不动。

    【讨论】:

    • 我喜欢你突出显示unique_ptr。但是您包含评论的行://Copy assignment is forbidden. 不正确。这是一个移动任务,unique_ptr 允许这样做。
    • @HowardHinnant:我以为我在发布之前已经删除了该部分(这就是我后来证明它有效的原因),但我没有全部删除。
    • g() 是prvalue,而不是xvalue。如果声明了g,例如std::unique_ptr&lt;SomeType&gt;&amp;&amp; g();,然后 g() 将是一个 xvalue。请注意,对于您的示例,g() 无论如何都是右值就足够了。
    【解决方案5】:

    如果你真的想要的话,你可以组合一个代理来完成这个技巧,并有一个转换构造函数来复制存储在代理中的值。

    类似的东西:

    template<typename T>
    struct ReturnProxy {
        //This could be made private, provided appropriate frienship is granted
        ReturnProxy(T* p_) : p(p_) { }
        ReturnProxy(ReturnProxy&&) = default;
    
    private:
        //don't want these Proxies sticking around...
        ReturnProxy(const ReturnProxy&) = delete;
        void operator =(const ReturnProxy&) = delete;
        void operator =(ReturnProxy&&) = delete;
    
        struct SUPER_FRIENDS { typedef T GO; };
        friend struct SUPER_FRIENDS::GO;
        unique_ptr<T> p;
    };
    
    struct Object {
        Object() : data(0) { }
    
        //Pseudo-copy constructor
        Object(ReturnProxy<Object>&& proxy)
          : data(proxy.p ? proxy.p->data : throw "Don't get sneaky with me \\glare") 
        {
          //steals `proxy.p` so that there isn't a second copy of this object floating around
          //shouldn't be necessary, but some men just want to watch the world burn.
          unique_ptr<Object> thief(std::move(proxy.p));
        }
    private:
        int data;
    
        Object(const Object&) = delete;
        void operator =(const Object&) = delete;
    };
    
    ReturnProxy<Object> func() {
        return ReturnProxy(new Object);
    }
    
    int main() {
        Object o(func());
    }
    

    不过,您可能在 03 中也可以使用 auto_ptrs。而且它显然不会阻止结果 Object 的存储,尽管它确实将您限制为每个实例一个副本。

    【讨论】:

    • 我试图想办法摆脱 ReturnProxy 中可公开访问的移动构造函数,但后来我意识到这是最初的问题,只是向后移动了一个级别。
    • 即使你这样做了,我也可以使用 auto 按值存储代理。 auto 可以声明私有类型等。
    猜你喜欢
    • 1970-01-01
    • 2012-04-23
    • 2019-10-08
    • 1970-01-01
    • 1970-01-01
    • 2013-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多