开发者

RVO/NRVO and public undefined copy constructor

开发者 https://www.devze.com 2023-02-22 09:18 出处:网络
I\'m fighting the following proposal now, and I want to know legal and for lesser extent moral arguments against it or for it.

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;

cla开发者_如何学Pythonss 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.


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


The obvious answer is that the compiler is under no obligation to elide the copy construction, especially if optimization is disabled (although g++ still elides the copy even without optimization). Thus, you're restricting portability.

Most likely if it compiles on a particular compiler it would function as expected though.

I know there are a ton of seemingly arbitrary/artificial restrictions on what you can do, but are you able to use a proxy holder for 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_;
};


This looks like something you wont get out of peoples heads on technical reasons alone (aka "But it compiles and works on our compiler!"), so maybe a conceptually simpler approach might be a good idea?

If your concern is the 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

vs.

C const & c = init();

the simplest (as in: no hacks and proxy stuff required) solution might be:

C c_mutable_object;
init(c_mutable_object);
C const& c = c_mutable_object;

// ... lots of code with read-only access to c
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号