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?

RED SOFT ADAIR
  • 12,032
  • 10
  • 54
  • 92

5 Answers5

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;
}
Fred Nurk
  • 13,952
  • 4
  • 37
  • 63
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...      
}
Fred Nurk
  • 13,952
  • 4
  • 37
  • 63
Nim
  • 33,299
  • 2
  • 62
  • 101
  • @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... – Nim Jan 31 '11 at 17:15
  • 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. – Fred Nurk Jan 31 '11 at 17:21
  • Although this is not the solution i was hoping for, its clearly the bast way to handle such situations in the current state. Thanks. – RED SOFT ADAIR Feb 03 '11 at 11:09
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>
{
    // ...
};
GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • 5
    +1 i'd upvote more if could. this answer goes to root causes instead of addressing just symptoms. – Cheers and hth. - Alf Jan 31 '11 at 17:44
  • @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? – Nawaz Jan 31 '11 at 18:39
  • @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. – GManNickG Jan 31 '11 at 19:12
  • 1
    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. – RED SOFT ADAIR Jan 31 '11 at 19:29
  • 2
    @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. – GManNickG Jan 31 '11 at 19:30
  • 1
    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. – Fred Nurk Feb 01 '11 at 07:27
  • @GMan: This uses a garbage value from mSource in do_copy (called from the copy ctor). – Fred Nurk Feb 01 '11 at 08:04
  • 2
    @Fred: **Oh**, duh. Hm, seems there is indeed no way, eh? – GManNickG Feb 01 '11 at 08:11
  • @GMan: Perhaps inverting the relationship is not always so "very incorrect". :) – Fred Nurk Feb 01 '11 at 08:25
  • @Fred: I still think it is. I think this nastiness is the result of having a poorly design program from the start. – GManNickG Feb 01 '11 at 08:28
  • 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. – Fred Nurk Feb 01 '11 at 08:30
  • @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.) – GManNickG Feb 01 '11 at 08:35
  • 1
    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. – Fred Nurk Feb 01 '11 at 10:16
  • @Fred: I never claimed anything required full knowledge, just that academics are, sadly, largely ignored. – GManNickG Feb 01 '11 at 10:20
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.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
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.

ashkan
  • 166
  • 1
  • 7