Вот некоторые идеи, которые могут помочь, если вы не можете использовать решение PIMPL из другого ответа. Они либо основаны на CRTP, либо на тегах конструктора:
#include <type_traits>
struct A;
template<typename T>
struct Base final {
};
// Specialise Base for A to be non-final
template<>
struct Base<A> {};
struct A : Base<A> {};
Идея первого фрагмента состоит в том, чтобы иметь final
CRTP-базу и добавить нефинальную специализацию для класса A
. Очевидно, что это позволяет пользователям (или вам самим) определять больше разрешенных производных, если это необходимо, но предотвращает случайное наследование.
template<char...>
struct password {};
struct Base2 {
Base2() = delete;
template<char... pwd>
Base2(password<pwd...> ) {
using expected_pwd = password<'t','o','p','s','e','c','r','e','t'>;
static_assert(std::is_same_v<password<pwd...>, expected_pwd>);
}
};
struct A2 : Base2 {
A2() : Base2(password<'t','o','p','s','e','c','r','e','t'>{}) {}
};
struct A3 : Base2 {
// No way to construct Base2 without correct password :)
A3() : Base2(password<'b', 'a', 'm', 'm'>{}) {}
};
Идея этого второго фрагмента (который больше шутит, чем реальный код ...) состоит в том, чтобы заменить конструктор по умолчанию класса Base2
структурой password
. В рамках реализации вы проверяете пароль, данный конструктору. В реализации вашего производного класса вы просто вызываете конструктор Base2
с правильным паролем. Конечно, ваши пользователи могут наследовать базовый класс, но так как пароль полностью скрыт в реализации, нет способа создать производные объекты:)
int main(){
auto a = A{};
auto a2 = A2{};
auto a3 = A3{}; // fails
return 0;
}