Предоставлять доступ только к экземплярам классов в C

#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, чтобы иметь к нему доступ.