【问题标题】:RVO/NRVO and public undefined copy constructorRVO/NRVO 和公共未定义复制构造函数
【发布时间】:2011-04-05 13:53:44
【问题描述】:

我现在正在反对以下提议,我想知道反对或支持它的法律和道德论据。

我们有什么:

#include <vector>

class T;

class C
{
public:
    C() { }
    ~C( ) { /*something non-trivial: say, calls delete for all elements in v*/ }
    // a lot of member functions that modify C
    // a lot of member functions that don't modify C
private:
    C(C const &);
    C& operator=(C const&);
private:
    std::vector< T* > v;
};

void init(C& c) { } // cannot be moved inside C

// ...
int main()
{
    // bad: two-phase initialization exposed to the clients
    C c;
    init(c);

    // bad: here follows a lot of code that only wants read-only access to c
    //      but c cannot be declared const
}

建议的内容:

#include <vector>

class T;

class C
{
public:
    C() { }
    ~C( ) { /*calls delete for all elements in v*/ }

    // MADE PUBLIC
    C(C const &); // <-- NOT DEFINED

    // a lot of member functions that modify C
    // a lot of member functions that don't modify C
private:
    C& operator=(C const&);
private:
    vector< T* > v;
};

C init() // for whatever reason object CANNOT be allocated in free memory
{
    C c;
    // init c
    return c;
}

// ...
int main()
{
    C const & c = init();
}

这使用最新的 g++(它是唯一的目标编译器)4.1.2 和 4.4.5 进行编译和链接(并且工作)——因为 (N)RVO 永远不会调用复制构造函数;析构函数仅在 main() 结束时调用。

据称该技术非常好,因为复制构造函数不可能被误用(如果它曾经生成过,那将是链接器错误),并且将其公开可以防止编译器抱怨私有一个。

使用这种技巧对我来说看起来真的非常错误,我觉得这与 C++ 精神相矛盾,而且看起来更像是 hack——从这个词的不好的意义上来说。

我的感觉没有足够的论据,所以我现在正在寻找技术细节。

请不要在此处发布教科书 C++ 内容:

  • 我知道“三法则”并已通读《圣标准》的 12.8/15 和 12.2;
  • vector&lt;shared_ptr&lt;T&gt; &gt;ptr_vector&lt;T&gt; 都不能用;
  • 我无法在空闲内存中分配C 并通过C*init 返回它。

谢谢。

【问题讨论】:

  • 我假设移动构造对你来说是不可能的?
  • @Konrad Rudolph:很遗憾没有(g++ 4.1.2 是我们应该支持的编译器之一,因此没有 0x 特性)。
  • “将其公开可防止愚蠢的编译器抱怨私有编译器。” - 不是愚蠢的编译器。该标准要求即使复制被省略,复制构造函数也可以访问,因为复制省略是可选的。因此,如果允许它是私有的,那么依赖省略来避免错误的程序无论如何都不会严格符合,在这种情况下,标准要求诊断。编译器正在帮助您编写可移植的代码,诚然违背您的意愿;-)
  • @Steve Jessop:我知道。抱歉,这是“声称的内容”的讽刺部分。我已将其从文本中删除,以防止人们关注不相关的细节。
  • 另一种选择:init 可以将(空)boost::optional 作为 by-ref 参数,然后将对象构造为此可选项,返回对内部对象的 const 引用升压::可选。在这种情况下,默认 ctor 也将是私有的,init 将成为 C 类型的 friend。 -- 只是一个想法。

标签: c++ g++ copy-constructor return-value-optimization


【解决方案1】:

这使用最新的 g++(它是唯一的目标编译器)4.1.2 和 4.4.5 进行编译和链接(并且工作)——因为 (N)RVO 永远不会调用复制构造函数;析构函数仅在 main() 结束时调用。

虽然它可能适用于 GCC,但您的代码确实具有未定义的行为,因为它引用了未定义的函数。在这种情况下,您的程序格式不正确;无需诊断。这意味着 GCC 可能会忽略违反规则,但其他编译器可能会对其进行诊断或执行其他奇怪的操作。

因此,基于这些理由,我会拒绝这种方式。

我的感觉没有足够的论据,所以我现在正在寻找技术细节。

你想在这里有移动语义。有这个明确的怎么办?

class T;
class C;

struct CMover {
  C *c;
private:
  CMover(C *c):c(c) { }
  friend CMover move(C &c);
};

class C {
public:
    C() { }
    ~C( ) { /*calls delete for all elements in v*/ }

    C(CMover cmove) {
      swap(v, cmove.c->v);
    }

    inline operator CMover();

    // a lot of member functions that modify C
    // a lot of member functions that don't modify C
private:
    C& operator=(C const&); // not copy assignable
    C(C &); // not lvalue copy-constructible

private:
    vector< T* > v;
};

CMover move(C &c) { return CMover(&c); }
C::operator CMover() { return move(*this); }

现在你可以说

C init() // for whatever reason object CANNOT be allocated in free memory
{
    C c;
    return move(c);
}

int main() {
  C const c(init());
}

【讨论】:

  • 你能告诉我C++03在哪里禁止引用未定义的函数吗?关于移动语义,在这种特殊情况下,我们不希望它那么糟糕:我的问题更多是关于为什么应该接受或禁止这个提议。
  • @Alex 3.2p3,该提案应该被拒绝,因为它不能移植。我理解你的问题,但我想除了回答你的问题之外,我还会告诉我如何解决它。
  • 谢谢,3.2/2 + 3.2/3 清楚地表明那是 UB。此外,我们刚刚发现 MSVC++ 2008 和 2010(现在不支持,但你永远不知道)拒绝此代码。
【解决方案2】:

显而易见的答案是编译器没有义务省略复制构造,尤其是在禁用优化的情况下(尽管即使没有优化,g++ 仍然会省略复制)。因此,您限制了可移植性。

如果它在特定的编译器上编译,它很可能会按预期运行。

我知道你可以做很多看似任意/人为的限制,但你可以为C 使用代理持有人吗?

class C
{
private:
    C() { }
    ~C( ) { /*calls delete for all elements in v*/ }

    // ...
    friend class C_Proxy;
};

class C_Proxy
{
public:
    C_Proxy() : c_() { init(c_); }

    // Or create a public const& attribute to point to this.
    const C& get_C() const { return c_; }

private:
    C c_;
};

【讨论】:

    【解决方案3】:

    这看起来像你不会仅仅因为技术原因而从人们头脑中得到的东西(又名“但它可以在我们的编译器上编译和工作!”),所以也许一个概念上更简单的方法可能是个好主意?

    如果你关心的是 constness ...

    C c;
    init(c);
    
    // bad: here follows a lot of code that only wants read-only access to c
    //      but c cannot be declared const
    

    对比

    C const & c = init();
    

    最简单的(例如:不需要黑客和代理的东西)解决方案可能是:

    C c_mutable_object;
    init(c_mutable_object);
    C const& c = c_mutable_object;
    
    // ... lots of code with read-only access to c
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-01
      • 1970-01-01
      • 2011-02-08
      • 2021-10-09
      • 1970-01-01
      • 1970-01-01
      • 2016-07-13
      • 2013-10-13
      相关资源
      最近更新 更多