Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C++, if copying an object is possible, but really expensive and usually a bad idea, should you still implement copying using a copy constructor?

Tags:

c++

I'm pretty new to C++, and I have a question regarding some C++ conventions regarding copying. I've googled around and haven't really been able to find good guidance on this, so I'm turning to you fine folks.

Let say you have an object that represents some resource that is technically copyable, but the copy is a expensive and almost always the wrong thing to do. Should you still implement a copy constructor for it? Or is it better to make a member function that is something like make_copy() (for those rare times when you DO want to copy the object).

For instance: lets say you have a class representing a texture stored in video memory. This resource is technically copyable: you can create a new handle for it and copy the memory (either through the CPU or using graphics library calls). But generally speaking, this is not something you really want to do very often. It's expensive, and it's usually the wrong thing to do, and is potentially very wasteful of memory. However, you could imagine corner cases where it would make sense: taking a screenshot and applying filters over it or something. Those cases would be rare, but they would exist.

The reason I'm hesitant making a copy constructor that does this this is that I feel like C++ is a bit too eager to copy stuff. Maybe it reflects the fact that I'm a bit new to the language, but since C++ will call the copy constructor in all sorts of situations where you might not mean for it to do so. Like:

void some_method(Texture t)
{
    ...
}

Texture t(<arguments>);
Texture t2 = t;        // calls copy constructor

some_method(t2);       // calls copy constructor

I would much rather these two lines that calls copy constructors be compiler errors (because I feel like they're very easy mistakes to make) and if you want to actually make a copy, you need to be very explicit about it and use a dedicated member function.

Is there some standard practice around this? Some sage advice on when you should (and shouldn't) write copy constructors? Some Scott Meyers chapter I've missed? Or should I just do it whenever it's possible?

EDIT: to be clear about my example: obviously, you should pass the argument by reference, but my point was that this is a very easy mistake to make, just leaving out the ampersand. And if you do that, the compiler will happily replace a cheap pass-by-reference with a very expensive copy, and I would rather the compiler not do that. But I dunno, maybe this isn't a mistake people generally make in C++, and I'm being overly cautious?

like image 724
Oskar Avatar asked Nov 06 '18 10:11

Oskar


People also ask

Why is it important to make a copy of a passed object in a constructor?

It is necessary to pass object as reference and not by value because if you pass it by value its copy is constructed using the copy constructor. This means the copy constructor would call itself to make copy. This process will go on until the compiler runs out of memory.

When would you use a copy constructor?

Uses of Copy ConstructorWhen we initialize an object by another object of the same class. When we return an object as a function value. When the object is passed to a function as a non-reference parameter.

What will happen if a copy constructor?

An implicitly defined copy constructor will copy the bases and members of an object in the same order that a constructor would initialize the bases and members of the object.


1 Answers

Whether you want to use it is your call, but here is a pattern that is relatively light on syntax:

struct ExplicitCopy {

    // Implement as usual
    ExplicitCopy() = default;
    ExplicitCopy(ExplicitCopy &&) = default;
    ExplicitCopy &operator = (ExplicitCopy &&) = default;

    // Copying happens with an ADL call to this function
    friend ExplicitCopy copy(ExplicitCopy const &orig) {
        return orig;
    }

private:
    // Copy operations are private and can't be called accidentally
    ExplicitCopy(ExplicitCopy const &) = default;
    ExplicitCopy &operator = (ExplicitCopy const &) = default;
};

Then, trying to copy an instance will result in a compiler error from the call to a private constructor.

ExplicitCopy ec;
ExplicitCopy ec2 = ec;       // Nope
ExplicitCopy ec3(ec);        // Nope
ExplicitCopy ec4 = copy(ec); // Yes

Thanks to copy elision, there is no additional constructor call.

See it live on Coliru

like image 72
Quentin Avatar answered Dec 01 '22 21:12

Quentin