How can I extend a compiler generated copy constructor
Asked Answered
I

5

21

I frequently run into the problem, that I must extend a compiler generated copy constructor. Example:

class xyz;
class C
{
    ...
    int a, b, c; 
    std::set<int> mySet;
    xyz *some_private_ptr;
};

Assume, that some_private_ptr should only be copied under certain conditions. For other conditions it should be reset to NULL on copy. So I have to write a copy constructor like:

C::C(const C &other) : 
     a(other.a), 
     b(other.b), 
     c(other.c), 
    mySet(other.mySet)
{      
   if(CanCopy(other.some_private_ptr)) // matches condition
      some_private_ptr = other.some_private_ptr;
   else
      some_private_ptr = NULL;
}

The problem is that the class might have a number of data members, and that I very likely may forget to update the copy constructor when I add a data member. It would be very nice if I just could write.

C::C(const C &other) :
   C::default_copy(other)
{      
   if(CanCopy(other.some_private_ptr)) // matches condition
      some_private_ptr = other.some_private_ptr;
   else
      some_private_ptr = NULL;
}

This would make my code more safe and easier to maintain. Unfortunately I don't know of such a possibility. Is there any?

Inrush answered 31/1, 2011 at 16:51 Comment(0)
C
17

the moment you define your own copy ctor, the compiler does not bother generating one for you. Unfortunately this means you have to do all the leg work yourself! You could group the members into some sort of impl_ structure within your class, and then rely on the copy ctor for that.

for example:

class xyz;
class C
{
  struct impl_
  {
    int a, b, c; 
    std::set<int> mySet;
    xyz *some_private_ptr;
  };

  impl_ data;
};

now in your copy ctor

C::C(const C &other) : data(other.data)
{
 // specific stuff...      
}
Cornish answered 31/1, 2011 at 16:57 Comment(3)
@Fred, thanks for the edit! I had meant to say "default copy ctor", but missed the copy, I guess it means the same thing now...Cornish
I knew what you meant, thought it was a simple typo. :) "Default" is tricky because "default ctor" has a specific meaning, "compiler-generated copy ctor" or just "generated cctor" is less ambiguous.Package
Although this is not the solution i was hoping for, its clearly the bast way to handle such situations in the current state. Thanks.Inrush
P
18

The easiest way is to introduce a base class:

class xyz;

struct CDetail {
  //...
  int a, b, c; 
  std::set<int> mySet;
  xyz *some_private_ptr;
};

struct C : private CDetail {
  C(C const &other)
  : CDetail(other)
  {
    if (!CanCopy(other.some_private_ptr))
      some_private_ptr = 0;
    // opposite case already handled
  }
};

This is an abuse of inheritance to an extent, but the advantages over a nested "impl" class are 1) you can access each member as "name" rather than "data.name" (reducing code changes when refactoring), and 2) (though only sometimes desired) you can "promote" individual members to protected or public without affecting other members:

struct C : private CDetail {
protected:
  using CDetail::a;
};

struct D : C {
  void f() {
    cout << a;
  }
};

int main() {
  D d;
  d.f();  // D can access 'a'
  cout << d.a;  // main cannot access 'a'
  return 0;
}
Package answered 31/1, 2011 at 17:2 Comment(1)
+1 for this nice idea.. seeing your and Nim's idea, I deleted my post :PTeddy
C
17

the moment you define your own copy ctor, the compiler does not bother generating one for you. Unfortunately this means you have to do all the leg work yourself! You could group the members into some sort of impl_ structure within your class, and then rely on the copy ctor for that.

for example:

class xyz;
class C
{
  struct impl_
  {
    int a, b, c; 
    std::set<int> mySet;
    xyz *some_private_ptr;
  };

  impl_ data;
};

now in your copy ctor

C::C(const C &other) : data(other.data)
{
 // specific stuff...      
}
Cornish answered 31/1, 2011 at 16:57 Comment(3)
@Fred, thanks for the edit! I had meant to say "default copy ctor", but missed the copy, I guess it means the same thing now...Cornish
I knew what you meant, thought it was a simple typo. :) "Default" is tricky because "default ctor" has a specific meaning, "compiler-generated copy ctor" or just "generated cctor" is less ambiguous.Package
Although this is not the solution i was hoping for, its clearly the bast way to handle such situations in the current state. Thanks.Inrush
C
12

The problem here is your class is trying to do too much. Either use a resource, or manage a resource. You don't do both, ever, because your code will become an unsafe, pile of slop. And that's no good.

You need to design a class that manages a resource that is only copied under certain conditions. You haven't really expanded on what those conditions on and why they're there in the first place (that's an awfully strange way to "copy" data, are you sure this is the best route?), but it'd be something like this:

// pointer to your condition member (not sure if this is even needed,
// is this condition knowable via the pointer alone? you get the point)
template <typename T, typename D, class Tag = void>
class copy_conditional_ptr
{
public:
    copy_conditional_ptr(bool (D::*condition)(T*) const, T* value = 0) :
    mCondition(condition),
    mValue(value)
    {}

    // here's where the unique copy-semantics go
    copy_conditional_ptr(const copy_conditional_ptr& other) :
    mCondition(other.mCondition),
    mValue(do_copy(other.mValue) ? other.mValue : 0)
    {}

    // other stuff for a smart pointer,
    // copy-and-swap, etc...

protected:
    // protected because it's meant to be a base class
    ~copy_conditional_ptr()
    {
        // whatever
    }

private:
    bool do_copy(T* value) const
    {
        const D& self = static_cast<const D&>(*this);
        return (self.*mCondition)(other.value);
    }

    bool (D::*mCondition)(T*) const;
    T* mValue;
};

Then you use it like this:

class xyz;

class C : private copy_conditional_ptr<xyz, C>
{
public:
    C() :
    /* others, */
    copy_conditional_ptr(&C::CanCopy)
    {}

private:
    int a, b, c; 
    std::set<int> mySet;
};

And let the management be automatic for the rest of the class. The tag is so you can have multiple in the same class:

class C : private copy_conditional_ptr<xyz, C, struct C_first>,
            private copy_conditional_ptr<xyz, C, struct C_second>
{
    // ...
};
Causal answered 31/1, 2011 at 17:30 Comment(14)
+1 i'd upvote more if could. this answer goes to root causes instead of addressing just symptoms.Phyllisphylloclade
@GMan: for this, you've to define CanCopy function, right?...also I don't understand how the management is automatic now? can you please explain a bit more?Teddy
@Nawaz: Yes, you still have to define it. The point is you define the policy, but not the management. Like in the same way you might specify a special deleter for unique_ptr, but not manage the actual resource.Causal
Although your thoughts about correctness are quite right, this approach does not solve the problem: Still i'll have to add each new member manually to the copy constructor. Still i may forget about this.Inrush
@Red: Why would you need to write a copy constructor at all? The entire point is correctly leave the custom copy-semantics to the resource management, so you don't have to do it yourself. The other solutions try to fix your problem by inverting this relationship, which is very incorrect.Causal
This is wrong: it copies mSource from other.mSource instead of updating to the correct "parent" object. Copying a first C to a second, then destroying the first, then copying the second to a third will lead to UB because second.some_private_ptr.mSource == &first.Package
@GMan: This uses a garbage value from mSource in do_copy (called from the copy ctor).Package
@Fred: Oh, duh. Hm, seems there is indeed no way, eh?Causal
@GMan: Perhaps inverting the relationship is not always so "very incorrect". :)Package
@Fred: I still think it is. I think this nastiness is the result of having a poorly design program from the start.Causal
Everything is not always within your control, and you often have to interface with code you didn't design. We'll not be able to address that here without a lot more code and context from the OP. However, I would still find it hard to call anything else incorrect when your "proper" solution is fundamentally broken.Package
@Fred: My correctness is independent from the correctness of other designs. Anyway, this may be another potential solution to an unnecessarily ugly problem. (Also, I tag "I'm working with bad code" as an exceptional case, one that doesn't influence what the correct solution is.)Causal
Unfortunately, "everything would be clean if designed cleanly from the beginning with full knowledge of how the entire systems will evolve" isn't the real world.Package
@Fred: I never claimed anything required full knowledge, just that academics are, sadly, largely ignored.Causal
R
0

I'd say create a smart pointer that handles the copying, and then use it as a member of your class. These codes may give you an idea:

Depending on how the base call constructor is initiated, the member's constructors will be called the same way. For example, let's start with:

struct ABC{
    int a;
    ABC() : a(0)    {   printf("Default Constructor Called %d\n", a);   };

    ABC(ABC  & other )  
    {
        a=other.a;
        printf("Copy constructor Called %d \n" , a ) ;
    };
};

struct ABCDaddy{
    ABC abcchild;
};

You can do these tests:

printf("\n\nTest two, where ABC is a member of another structure\n" );
ABCDaddy aD;
aD.abcchild.a=2;

printf( "\n Test: ABCDaddy bD=aD;  \n" );
ABCDaddy bD=aD; // Does call the copy constructor of the members of the structure ABCDaddy ( ie. the copy constructor of ABC is  called)

printf( "\n Test: ABCDaddy cD(aD); \n" );
ABCDaddy cD(aD);    // Does call the copy constructor of the members of the structure ABCDaddy ( ie. the copy constructor of ABC is  called)

printf( "\n Test: ABCDaddy eD; eD=aD;  \n" );
ABCDaddy eD;
eD=aD;          // Does NOT call the copy constructor of the members of the structure ABCDaddy ( ie. the copy constructor of ABC is not called)

Output:

Default Constructor Called 0

Test: ABCDaddy bD=aD;
Copy constructor Called 2

Test: ABCDaddy cD(aD);
Copy constructor Called 2

Test: ABCDaddy eD; eD=aD;
Default Constructor Called 0

Enjoy.

Ringler answered 31/8, 2011 at 17:48 Comment(0)
E
0

You might place your special member in a base class, something like:

class xyz;

class SpecialCopyXYZ
{
public:
    SpecialCopyXYZ() = default;
    SpecialCopyXYZ(const SpecialCopyXYZ& rhs)
    {
       if (CanCopy(other.some_private_ptr)) {
          some_private_ptr = other.some_private_ptr;
       } else {
          some_private_ptr = nullptr;
       }
    }

    // SpecialCopyXYZ& operator=(const SpecialCopyXYZ& rhs)

protected:
    xyz *some_private_ptr = nullptr;
};


class C : private SpecialCopyXYZ
{
public:
    C(const C &other) = default;
private:
    int a, b, c; 
    std::set<int> mySet;
};

If SpecialCopyXYZ need C data, you may use CRTP and downcast.

Esteban answered 8/3, 2017 at 19:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.