Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to issue compile error if object is stack created (including inherited types)?

Tags:

c++

c++17

factory

Basically I want all sub-types to be created through a factory method (I have a tall domain hierarchy with some 200+ classes).

For new, it is not an issue since this can be overridden in A (making new private).

class A{
protected:
  A();
public:
  template<class T, typename... ARGUMENTS>
  static T* create(ARGUMENTS&&... arguments);
};

class B : public A {
public:
  B();
};

void test() {
  B b;//compile error wanted here - but as a consequence of inheriting A
}

Here A is a the "library/framework" class. Whereas B is a "user created class". It might be ok to require a typedef or similar on B.

UPDATE: I added the 'create' function on A that I intent to use for creating objects.

like image 920
darune Avatar asked Jan 07 '19 08:01

darune


2 Answers

You can require a token in the construction of A that is only passed in the body of A::create

#include <utility>

class A{
private:
  struct create_token
  { 
    create_token(const create_token &) = delete; 
    create_token& operator=(const create_token &) = delete; 
    create_token(create_token &&) = default; 
    create_token& operator=(create_token &&) = default; 
  };
protected:
  A(create_token) {}
public:
  template<class T, typename... ARGUMENTS>
  static T* create(ARGUMENTS&&... arguments)
  {
    // Whatever creation mechanism here
    return new T(create_token{}, std::forward<ARGUMENTS>(arguments)...);
  }
};

class B : public A {
public:
  template <typename Token> // Can't name A::create_token, it is private
  B(Token tok) : A(std::move(tok)) {}
  B(){} // Will always lack a `create_token`
};

int main() {
  B b;//compile error wanted here - but as a consequence of inheriting A
  B* b = A::create<B>();
}

See it live

like image 119
Caleth Avatar answered Oct 19 '22 02:10

Caleth


This is another approach which relies on checking if the derived class constructor is private. But honestly, I prefer the solution given by @Caleth

#include <type_traits>
#include <iostream>
#include <type_traits>


template<typename T, typename... Args>
struct constructor_tag{};



class A{
protected:


    template<typename T, typename... Args>
    A(constructor_tag<T, Args...>) {
        static_assert(!std::is_constructible_v<T, Args...>, "CONSTRUCTOR MUST NOT BE PUBLIC");
    };


public:
    template<class T, typename... ARGUMENTS>
    static T* create(ARGUMENTS&&... arguments) {
        return new T(std::forward<ARGUMENTS>(arguments)...);
    }
};

class B : public A {

    friend class A;
    B() : A(constructor_tag<B>{}) {}
public:
};

class C : public A {
    friend class A;

    C () : A(constructor_tag<C>{}) {}

    C(int) : A(constructor_tag<C, int>{}) {}
public:

};


// Following class will not compile because the constructor is public

//class D : public A {
//    friend class A;
//
//public:
//    D () : A(constructor_tag<D>{}) {}
//
//};

void test() {

    // B b; //calling a private constructor of class 'B'
    // C c(5);//calling a private constructor of class 'A'

    A::create<B>();
    A::create<C>(5);
    A::create<C>();
}

int main() {


    test();
}
like image 1
florestan Avatar answered Oct 19 '22 03:10

florestan