Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 567
Alexander Poluektov Avatar asked Apr 05 '11 13:04

Alexander Poluektov


People also ask

Is NRVO guaranteed?

Compilers often perform Named Return Value Optimization (NRVO) in such cases, but it is not guaranteed.

What is NRVO C++?

Bright implemented this optimization in his Zortech C++ compiler. This particular technique was later coined "Named return value optimization" (NRVO), referring to the fact that the copying of a named object is elided.

What are copy constructors in C++?

A copy constructor is a member function that initializes an object using another object of the same class. In simple terms, a constructor which creates an object by initializing it with an object of the same class, which has been created previously is known as a copy constructor.

What is copy elision in Javascript?

Copy elision is an optimization implemented by most compilers to prevent extra (potentially expensive) copies in certain situations. It makes returning by value or pass-by-value feasible in practice (restrictions apply).


1 Answers

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());
}
like image 141
Johannes Schaub - litb Avatar answered Sep 28 '22 20:09

Johannes Schaub - litb