RVO/NRVO和公共未定义复制构造函数 (注:该内容需要进一步完善,以便更好地理解上下文。)

9
我目前正在反对以下提案,我想知道针对它的法律和较小程度的道德论据,或者支持它的论据。
我们之前拥有的是:
#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()结束时被调用。
据称,这种技术完全没有问题,因为复制构造函数无法被误用(如果它曾经被生成过,那么链接器会报错),而将其设置为public可防止编译器抱怨私有成员。
我认为使用这样的技巧看起来非常错误,我觉得它与C++精神相矛盾,更像是hack——在负面意义上。
我的感觉并不足以成为论据,所以现在我正在寻找技术方面的支持。
请不要在此处发布关于C++教科书的内容:
- 我知道“三大法则”,并已经阅读了《Holy Standard》的12.8/15和12.2节; - 我既不能使用vector<shared_ptr<T> >也不能使用ptr_vector<T>; - 我不能在自由内存中分配C,并通过C*init返回它。
谢谢。

1
我假设对于你来说,移动构造不是一个可行的选择? - Konrad Rudolph
@Konrad Rudolph:很遗憾(我们需要支持g++ 4.1.2这个编译器,因此不能使用0x特性)。 - Alexander Poluektov
5
将其公开可以防止愚蠢的编译器抱怨私有的那一个。- 不是愚蠢的编译器。即使复制被省略,标准要求复制构造函数也必须是可访问的,因为复制省略是可选的。如果允许它是私有的,那么依赖省略以避免错误的程序将不是严格符合规范的,在这种情况下,标准要求进行诊断。编译器正在帮助您编写可移植代码,尽管违背了您的意愿;-) - Steve Jessop
@Steve Jessop:我知道。抱歉,那是“所声称的”的讽刺部分。我已经从文本中删除它,以防止人们关注不相关的细节。 - Alexander Poluektov
2
另一种选择:init 可以将一个(空的)boost::optional<C> 作为按引用传递的参数,并将对象构造到此可选项中,返回 boost::optional 中对象的 const 引用。在这种情况下,默认构造函数也将是私有的,而 init 将成为 C 类型的友元函数。-- 这只是一个想法。 - Martin Ba
3个回答

10
这段代码可以使用最近的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在哪里禁止引用未定义的函数?关于移动语义,我们在这种特定情况下并不是非常需要它:我的问题更多地是关于为什么应该接受或禁止这个提案。 - Alexander Poluektov
4
3.2p3提案应该被拒绝,因为它不具备可移植性。我理解你的问题,但是除了回答你的问题外,我还想告诉你我如何解决它。 - Johannes Schaub - litb
感谢您。3.2/2 + 3.2/3 明显表明那是未定义行为。同时,我们刚刚发现 MSVC++ 2008 和 2010(虽然它们现在不受支持,但你永远不知道)拒绝接受这段代码。 - Alexander Poluektov

0
显而易见的答案是编译器没有义务省略复制构造函数,特别是如果禁用了优化(尽管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_;
};

0

这似乎是一些人仅凭技术原因就无法摆脱的东西(也就是“但它在我们的编译器上可以编译和工作!”),所以也许采用概念上更简单的方法可能是个好主意?

如果您担心的是const属性...

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接