0

I have some third-party abstract base class

struct foo
{
    virtual foo*job() = 0;
    ...
    static void* make_space(size_t sizeofDerived);
};

which I cannot alter. Objects of type foo (and all derived classes) must only be created/constructed using placement-new into the memory returned by foo::make_space(), because ordinary construction, as in

derived_from_foo z;                 // or
auto p = new derived_from_foo();

may cause undesired problems (corrupting memory, depending on the compiler, as I found out eventually). So my question: how can I write/design a derived class

struct bar  // still abstract, since foo::job() is not provided
  : foo
{
    ...
    template<typename Derived, typename...Args>
    static Derived* create(Args&&...args)
    {
        return ::new(foo::make_space(sizeof(Derived)))
            Derived(std::forward<Args>(args)...);
    }
};

such that construction of objects of type bar or any type derived from bar by any other means than through bar::create() fails at (i) compilation or (ii, less ideal) at run-time?

Walter
  • 44,150
  • 20
  • 113
  • 196

1 Answers1

1

You can in fact enforce it, at a price.

Consider this class:

  class A
  {
      public:
          class Tag
          {
              public:
                  Tag(const Tag&) = default;
                  Tag(Tag&&) = default;
              private:
                  Tag() {}
                  friend class A;
          };

          A(Tag, int a, char b) : a(a), b(b) {}
          int a;
          char b;

          template<typename T, typename ... Params>
          static T* make(Params&& ... params)
          {
              return new T(Tag(), std::forward<Params>(params)...);
          }
  };

Its constructor requires a Tag parameter, but you cannot make a Tag, it's got a private constructor. You can derive from A, but you cannot directly create objects of a derived class either: it needs to pass a Tag to its parent constructor, and you cannot make one.

So the only way to create an object of A or a derived class is by calling A::make.


Well there is still a way to cheat

class B : public A
{
    public:
        B(Tag t, double q) : A(t, 42, 'z'), q(q) {
           // cheating:
           B* other = new B(t, 3.14);
        }
        double q;
};

If this bothers you, you still can enforce correctness at run-time by making Tag non-reusable à la std::unique_ptr. Remove the copy ctor, throw in a private bool flag that you set on construction and clear on move-out, and check it inside make.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243