Search code examples
c++g++copy-constructorreturn-value-optimization

RVO/NRVO and public undefined copy constructor


I'm fighting the following proposal now, and I want to know legal and for lesser extent moral arguments against it or for it.

What we had:

#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
}

What has been proposed:

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

This compiles and links (and works) using recent g++ (which is the only target compiler) both 4.1.2 and 4.4.5 -- because of (N)RVO the copy-constructor is never called; destructor is called at the end of main() only.

It is claimed that the technique is perfectly fine, because there is no way copy-constructor could be mis-used (if it ever have been generated it would be linker error), and making it public prevents compiler for complaining about private one.

It looks really-really wrong for me to use such trick, which I feel contradicts the C++ spirit and looks more like hack -- in the bad sense of the word.

My feelings is not sufficient argumentation, so I'm looking for technicalities now.

Please don't post textbook C++ stuff here:

  • I'm aware of "The Rule of Three" and have read through 12.8/15 and 12.2 of Holy Standard;
  • I can use neither vector<shared_ptr<T> > nor ptr_vector<T>;
  • I cannot allocate C in free memory and return it from init via C*.

Thank you.


Solution

  • This compiles and links (and works) using recent g++ (which is the only target compiler) both 4.1.2 and 4.4.5 -- because of (N)RVO the copy-constructor is never called; destructor is called at the end of main() only.

    While it may work with GCC, your code really has undefined behavior because it references a function that's not defined. In such a case, your program is ill-formed; no diagnostic required. Which means that GCC may ignore the rule violation, but other compilers may diagnose it or do something else strange.

    So on those grounds, I would reject this way.

    My feelings is not sufficient argumentation, so I'm looking for technicalities now.

    You want to have move semantics here. What about having this explicit?

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

    Now you can say

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