How can I extend a compiler generated copy constructor

廉价感情. 提交于 2019-11-30 07:54:54

问题


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?


回答1:


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...      
}



回答2:


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



回答3:


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>
{
    // ...
};



回答4:


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.




回答5:


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.



来源:https://stackoverflow.com/questions/4853611/how-can-i-extend-a-compiler-generated-copy-constructor

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!