#c #c 11
Вопрос:
Я пытаюсь определить класс, в котором запрещено явное создание экземпляров. Пользователь должен иметь возможность использовать только ограниченный набор экземпляров.
С точки зрения кода, в настоящее время я делаю что-то вроде этого:
class Obj
{
private:
int _x;
Obj(int x) : _x() {}
public:
static const Obj obj1;
static const Obj obj2;
};
const Obj Obj::obj1(1);
const Obj Obj::obj2(2);
int main()
{
Obj o1 = Obj::obj1;
}
Конструктор является частным, и экземпляры классов доступны через Obj::xxx
него .
Однако при таком подходе мне не нравится тот факт, что экземпляр виден из другого.
(т. е. я не хочу Obj::obj1.obj2.obj1
быть допустимым синтаксисом)
Есть ли какой-либо обходной путь или лучшие шаблоны, чтобы избежать такого поведения?
Ответ №1:
Общедоступные статические члены (функции или объекты) всегда можно найти во время поиска из экземпляра этого класса, поэтому единственный способ избежать этого-не предлагать их в качестве static
членов из предоставляемого вами типа.
Из-за вашего требования хранить Obj
конструктор в секрете, нам нужно friend
где-то использовать ship, чтобы внешнее тело могло предоставлять эти объекты и доступ Obj
к конструктору, не будучи также типом Obj
(что позволяет создавать цепочки).
По этой причине я предлагаю вторичный class
struct
тип или, чтобы его можно было friend
изменить, чтобы он мог вызывать Obj
конструкторы:
class ObjConstants;
class Obj
{
// Note: friendship here so that ObjConstants can construct it
friend ObjConstants;
private:
int _x;
Obj(int x) : _x() {}
};
class ObjConstants {
public:
static const Obj obj1;
static const Obj obj2;
};
const Obj ObjConstants::obj1(1);
const Obj ObjConstants::obj2(2);
int main()
{
Obj o1 = ObjConstants::obj1;
}
Это также может быть класс, содержащий static
заводские функции:
class ObjConstants {
public:
static const Objamp; getObj1();
static const Objamp; getObj2();
};
или даже просто набор namespace
заводских функций с областью действия, каждая из которых добавляется отдельно:
const Objamp; getObj1();
const Objamp; getObj2();
class Obj {
friend const Objamp; getObj1();
friend const Objamp; getObj2();
...
};
Когда вы используете внешний держатель (будь то тип или функция) для статических объектов, каждый доступ к объекту возвращает тип, отличный от типа держателя (в данном случае Obj
), что предотвращает возможность цепочки obj1.obj2.obj1
Также стоит отметить, что этот подход также работает с static constexpr
объектами. Вы не можете определить static constexpr
объекты в теле своего собственного класса, так как в этот момент класс является неполным, например:
class Foo {
public:
static inline constexpr auto foo = Foo{}; // Error: 'Foo' is incomplete!
};
И поэтому использование вторичного типа держателя (будь то a class
или a namespace
) позволяет завершить определение к этому моменту:
class Foo { ... };
class FooConstants {
public:
static inline constexpr auto foo = Foo{...};
};
Примечание:
В общем, я бы рекомендовал static const
не использовать объекты с такой внешней связью, чтобы избежать проблем со статической инициализацией
Ответ №2:
Вам нужен вариант заводского шаблона здесь. Это в основном статический метод, который имеет доступ к вашему частному конструктору, но диктует условия того, что возвращается. Смотреть ниже:
class Obj
{
private:
int _x;
Obj(int x) : _x(x) {}
// Do the two lines below if you want, you'll catch more compiler errors that way
// Obj(const Objamp;) = delete; // Delete copy constructor
// Objamp; operator=(const Objamp;) = delete; // Delete assignment operator
public:
static Objamp; getObj1();
static Objamp; getObj2();
};
Objamp; Obj::getObj1()
{
static Obj obj1{ 1 }; // Only initialized when getObj1() is called, and only once
return obj1;
}
Objamp; Obj::getObj2()
{
static Obj obj2{ 2 }; // Only initialized when getObj2() is called, and only once
return obj2;
}
int main()
{
Objamp; ref_o1 = Obj::getObj1(); // Requires a reference
//Obj notref_o1 = Obj::getObj1(); // Fails to compile, not a reference!
Objamp; ref_o2 = Obj::getObj2();
Objamp; ref_other_o1 = Obj::getObj1(); // References the SAME object as ref_o1
}
Вы также можете заставить свою фабрику вместо этого использовать аргумент для нужного вам экземпляра и, если хотите, хранить там статическую коллекцию ваших объектов, хотя вам может потребоваться предоставить доступ к контейнеру для копирования/назначения/уничтожения. Но если вам нужен замкнутый набор, то то, что указано выше, должно быть хорошим планом для вас.
Мой описанный выше метод размещения выделения в статическом методе (не глобально) также позволяет избежать проблемы сбоя статического порядка инициализации, на которую ссылается человек-компилятор.
Комментарии:
1.OP пытается избежать того, чтобы вызывающий абонент связывал 1 статический элемент с другим (например
obj1.obj2.obj1
). Как это решает эту проблему? Этот подход делает его действительным, чтобы сделатьObj::getObj1().getObj2().getObj1();
см. Здесь2. решение этого вопроса состоит в том, чтобы просто не использовать статические методы. Остальное-это в основном мнения. Создавайте
getObj1
иgetObj2
освобождайте функции, и ваше решение «отлично» (хорошая ли локальная статика функций-это другой вопрос)3. Как сказал в ответе «прайм», это могут быть бесплатные функции, которые вместо этого являются друзьями. Это тоже работает. Экземпляры по-прежнему «знают» друг о друге столько же, сколько любой, кто читает API, знает друг о друге. Это зависит от операции, какой степени изоляции они хотят. Однако у вас всегда будет тот, кто читает «внешний» API, чтобы иметь к нему доступ.