Jump to content
  • Advertisement
Sign in to follow this  
irreversible

Preventing a polymorphic class from being constructed directly

This topic is 1007 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Some background:

 

I have a base class whose instances I want to keep track of. For this I'm using a private "link" structure, which stores a smart pointer to the object, which is then aliased ad libitum using raw pointers (within managed scope, though). I've forced myself into this corner, because my memory manager returns a compound pointer, which I do not wish to pass around in this case. I therefore need to "register" each instance of the class, which I want to do immediately after construction.

 

For this I have a kind of a library class that can load and call factory functions and register the returned object before passing back a raw handle. But it also, out of convenience, has a templated CreateHandle() procedure which is used to instantiate arbitrary custom classes that derive from base. CreateHandle() thus creates a new object of type T, stores its reference counted pointer in its own link structure, adds the link structure to a linked list and returns a raw alias. 

 

The question:

 

Except that now I wish to disable explicit or implicit construction of base outside of my library class. I could do that by making the constructor private, but that would disallow inheritance. Making it protected disallows explicit construction, but still allows the user to inherit base and create an instance of the derived object.

 

I can't add registration to the constructor of base, because 1) the library class isn't necessarily unique and its handle is not known (well, that's because I'm not passing it in, but still) and 2) base doesn't know its own shared pointer at construction time and the whole reason I'm thinking about this is because I can't think of a way to force the user to pass it in then or later (*see side note below). 

 

While a working solution would be to just assume that the programmer is not malicious or an idiot and uses CreateHandle() to construct their object, it'd be interesting to know if there's a roundabout mechanism in C++, which would allow me to limit construction to the methods I've explicitly provided (in this case the library class).

 

 

 

*As a side note: my concern stems from the fact that registration isn't integral (or even necessary) to the proper functioning of the application, but since base stores the only copy of its smart pointer in itself, it is necessary to avoid memory leaks.

 

Share this post


Link to post
Share on other sites
Advertisement

I don't know if your approach is sane (personally, it is not easy to follow your explanation without some example code), but regarding routing construction through some factory functions I will quote myself (http://www.gamedev.net/topic/675023-review-component-based-architecture-api/) in a slightly modified version:

 

struct Bar {
private:
  Bar() { }
  friend class Registry;
};

class Foo {
public:
  Foo(const Bar& bar);
};

class Derived: public Foo {
public:
  Derived(const Bar& bar, int a, int b) : Foo(bar) {
    // ...
  }
};

class Registry {
public:
  template<typename T, typename... Args>
  static T* Create(Args&&... args) {
    auto obj = new T(bar_, std::forward<Args>(args)...);
    // Your registration code here
    return obj;
  }
private:
  static Bar bar_;
};

This forces Derived to take a parameter to Bar, which necessarily routes any instantiation through Foo::Create.

The user can still shoot herself in the foot if she really wants to (by reinterpret_casting anything to an instance of the placeholder struct), but it can't be done by accident.

Share this post


Link to post
Share on other sites

I don't know if your approach is sane (personally, it is not easy to follow your explanation without some example code), but regarding routing construction through some factory functions I will quote myself (http://www.gamedev.net/topic/675023-review-component-based-architecture-api/) in a slightly modified version:

 

struct Bar {
private:
  Bar() { }
  friend class Registry;
};

class Foo {
public:
  Foo(const Bar& bar);
};

class Derived: public Foo {
public:
  Derived(const Bar& bar, int a, int b) : Foo(bar) {
    // ...
  }
};

class Registry {
public:
  template<typename T, typename... Args>
  static T* Create(Args&&... args) {
    auto obj = new T(bar_, std::forward<Args>(args)...);
    // Your registration code here
    return obj;
  }
private:
  static Bar bar_;
};

This forces Derived to take a parameter to Bar, which necessarily routes any instantiation through Foo::Create.

The user can still shoot herself in the foot if she really wants to (by reinterpret_casting anything to an instance of the placeholder struct), but it can't be done by accident.

 

 

That's pretty neat! It only requires one additional macro for the constructor to hide the semantics, which is fine. 

 

As for the sanity part - yeah, I've been pondering the same thing and at the end of the day it's just a way to solve a problem. The closed form in this case isn't even tied to the issue at hand - since I need to maintain a list of the created objects anyway and since pointers to these are bound to be aliased every which way anyway, I simply decided to remove automatic memory management (or the possibility of dangling copies of the shared pointer) from the equation completely and trust the application keeps track of when stuff gets destroyed. It makes no practical difference where I keep the smart pointer - in some array/list inside the registry class or within the object itself.

Share this post


Link to post
Share on other sites
 

Making [the constructor] protected disallows explicit construction, but still allows the user to inherit base and create an instance of the derived object.

 

That was my first thought; why wouldn't that work for your situation?

 

 

 

Bases that are pure virtual cannot be instanced explicitly. Can you make your destructor pure-virtual and then provide a definition anyway?

class Base
{
   public:
   Base();
   Base(Whatever whatever);

   virtual ~Base() = 0; //Pure virtual! Cannot construct except through derived classes.
};

//But we define it anyway, because when a derived class destroys itself,
//it'll also need to be able to destroy the base class, and we don't want that function pointer to be literally null.
Base::~Base() { }

Share this post


Link to post
Share on other sites

 

 

 

 

Making [the constructor] protected disallows explicit construction, but still allows the user to inherit base and create an instance of the derived object.

 

That was my first thought; why wouldn't that work for your situation?

 

 

 

Bases that are pure virtual cannot be instanced explicitly. Can you make your destructor pure-virtual and then provide a definition anyway?

class Base
{
   public:
   Base();
   Base(Whatever whatever);

   virtual ~Base() = 0; //Pure virtual! Cannot construct except through derived classes.
};

//But we define it anyway, because when a derived class destroys itself,
//it'll also need to be able to destroy the base class, and we don't want that function pointer to be literally null.
Base::~Base() { }

 

 

Sorry for the delay. The short answer to your question is - because that would defeat the purpose. Initializing just the the base class indirectly isn't what I'm trying to enforce. I need all objects to be registered, regardless of what level of inheritance they have. So it's not the construction I want to limit, but rather the scope within which construction can occur.

 

PS - your pure virtual destructor code is interesting, although I'm afraid it isn't quite what I need (I still need the base class to be constructible directly, because it's often a valid and desirable thing to do - I just want to it to be possibl only within the factory class).

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!