Home > other >  Let the user pass a user-defined type as sub-class of the parameter class to define a member
Let the user pass a user-defined type as sub-class of the parameter class to define a member

Time:02-11

The following is some architecture I have designed. I have a class X that has a variable member Y that is a pointer (or reference, I haven't decided yet) to class A. Class A is an abstract class. The user of X can create his own derived class from A (for example B:A) and pass it to X (probably, in the constructor) which, somehow, will store it in Y.

To make this work, the user should dynamically allocate an object of type B:A and pass it to X. However, can you think of a simpler way, for the user, of doing this without having to call new? Ideally, I would like the user to simply create an object of type B:A and pass it. Then, the constructor of X, somehow, would define Y using it (maybe creating a copy of B:A). The problem is that X doesn't know which derived type is passed and what size it is.

I want to create a single constructor to which the user could pass any type derived from A as parameter, and would be converted into a member variable. It should allow the user to create his own type for taking advantage of polymorphism.

   class A {...};   // Abstract class (has pure virtual members)

   class B : public A {...};   // Class defined by the user

   class X
   {
   public:
      X(A &param) { /* abc is defined */ }
      A* abc;
   }

Some ideas I had: Could it work a pure virtual copy assignment operator overloading at A? And having a member at B:A that specifies the size of B:A? However I still don't know how to make it work.

How to solve it? Is there maybe a better way? Thanks in advance.

CodePudding user response:

On the assumption that class X is going to own param after it is passed in, you can do this:

#include <iostream>
#include <memory>

class A { public: virtual ~A () {} };

class B : public A { public: ~B () { std::cout << "B destroyed"; } };

class X
{
public:
    X (std::unique_ptr <A> &param) : m_param (std::move (param))  { }
private:
    std::unique_ptr <A> m_param;
};

int main ()
{
    std::unique_ptr <A> b = std::make_unique <B> ();
    X x (b);
}

When run, this code prints B destroyed. Note that, for this to work, A must have a virtual destructor.

If ownership of param is to be shared with the caller and / or other objects, use std::shared_ptr instead (but this has more overhead). Or, as @Remy says, if you can guarantee that the lifetime of param exceeds that of x, you can store a raw pointer (or reference).

CodePudding user response:

To make this work, the user should dynamically allocate an object of type B:A and pass it to X. However, can you think of a simpler way, for the user, of doing this without having to call new?

Polymorphism is not dependent on new being used. It simply requires a pointer/reference to the polymorphic object. The caller could create its derived object statically, if it wants to. Just so long as the object outlives the X object that refers to it.

Then, the constructor of X, somehow, would define Y using it

That is not possible. The Y member would have to be statically typed at compile-time, ie as an A* pointer or A& reference. Unless X is written as a template class, so that the user can then specify the actual derived type being passed in.

maybe creating a copy of B:A

That is possible, but only if X is templated, otherwise A will have to declare a virtual clone() method that all derived classes override to make copy of themselves.

The problem is that X doesn't know which derived type is passed and what size it is.

Polymorphism doesn't need to know that info.

CodePudding user response:

You could simply require the class to implement the copy operation (the Clone function below):

class A
{
public:
    virtual ~A() = default;

    virtual std::unique_ptr<A> Clone() const = 0;
};

class B : public A
{
public:
    std::unique_ptr<A> Clone() const override
    {
        return std::make_unique<B>(*this);
    }
};

class X
{
public:
   X(A const& param)
      : abc(param.Clone())
   {
   }

   // allow transferring an object stored in a unique_ptr to the object
   template<class T> requires (std::is_base_of_v<A, T>)
   X(std::unique_ptr<T>&& param)
       : abc(std::move(param))
   {}
private:
   std::unique_ptr<A> abc;
};

Note that if you restrict the user to transferring the ownership to of the subclass of A to X, you don't need to Clone functionality at all. You you could remove X::X(A const&) and Clone in this case. The user would still be able to create your object like this:

X x = std::make_unique<B>();
X x(std::make_unique<B>()); // alternative syntax for the above
  • Related