2014-09-18 3 views
5

Простите меня, если это уже было задано, я не нашел ответов на свой конкретный вопрос.Ограничение доступа к конструктору и деструктору C++

У меня есть класс в библиотеке Я делаю это, чтобы определенные классы могли создавать и уничтожать, а другие классы - иметь доступ к другим публичным функциям. Наличие friend class не то, что я хочу, так как класс friend получит доступ к переменным-членам и функциям-членам, которых я не хочу. Я наткнулся на this idiom, который почти работает, за исключением деструктора, поскольку он не может принимать дополнительные параметры. С этой идиомой, я получаю:

class B; 
class A 
{ 
    public: 
     class LifecycleKey 
     { 
      private: 
       LifecycleKey() {} 
       friend class B; 
     }; 

     A(LifecycleKey); // Now only class B can call this 
     // Other public functions 

    private: 
     ~A(); // But how can I get class B to have access to this? 

     void somePrivateFunction(); 

     // Members and other private functions 
}; 

Как упоминался в приведенной выше коде, решение не только позволит class B иметь доступ к деструктору.

В то время как ни одна из вышеперечисленных проблем не является нарушителем транзакций на любом участке, поскольку я всегда могу просто сделать ctor и dtor public и просто сказать «RTFM».

Мой вопрос:

Есть какой-то способ, чтобы ограничить доступ к CTOR и dtor конкретных классов (но только CTOR и dtor), придерживаясь более хорошо известный синтаксис (имеющий вещи быть на стек, если люди хотят, уничтожая через удаление и т. д.)?

Любая помощь очень ценится!

РЕШЕНИЕ

в A.h

class B; 
class A 
{ 
    protected: 
     A() {} 
     virtual ~A() {} 
     A(const A&); // Implement if needed 
     A(A&&); // Implement if needed 

    public: 
     // Public functions 

    private: 
     void somePrivateFunction(); 

     // Members and other private functions 
}; 

в B.h

class B 
{ 
    public: 
     B(); 
     ~B(); 
     const A* getA() const; 

    private: 
     A* m_a; 
} 

в B.cpp

namespace { 
    class DeletableA : public A { 
     public: 
      DeletableA() : A() {} 
      DeletableA(const DeletableA&); // Implement if needed 
      DeletableA(DeletableA&&); // Implement if needed 
      ~DeletableA() {} 
    } 
} 

#include B.h 
B::B() : m_a(new DeletableA()) {} 
B::~B() { delete static_cast<DeletableA*>(m_a); } 
const A* B::getA() const { return m_a; } 

В качестве альтернативы, если DeletableA класса необходим в B.h или A.h (из-за встраивание, шаблоны, или желание иметь все class A связанные классы в A.h), он может быть перемещен туда с «ключом доступа» на конструкторе так нет другие классы могут его создать. Несмотря на то, что деструктор будет выставлен, ни один другой класс никогда не получит DeletableA для удаления.

Очевидно, что это решение требует, чтобы class B знать, чтобы сделать экземпляры Deletable A (или сделать класс в целом, если она не подвергаются в A.h) и хранить только A*, которые подвергаются через общественные функции, но, это самое которая была предложена.

Пока еще возможно для некоторых других классов, чтобы сделать подкласс class Aclass A не является «окончательным»), вы можете добавить еще один «ключ» передать в конструктор А, чтобы предотвратить такое поведение, если вы хотите.

+3

Это много вопросов в одном вопросе. – 101010

+3

Не имеет ли частный деструктор немного рывок? «Вот объект, вы не можете его удалить». – tadman

+0

Ну, это концептуально один вопрос, но несколько подробный. Хотите, чтобы я укоротил его? Я отредактирую, чтобы сократить его. – EncodedNybble

ответ

1

Для той цели, которую класс B должен быть единственным в состоянии создать экземпляр и уничтожить объекты класса A:

  • Для статической и автоматической переменной, ограничение доступа к конструктору это все, что нужно , и вы уже это делаете.

  • Для динамически распределяемого объекта Вы можете ограничить доступ к своим функциям открепления, operator delete и operator delete[] и оставить деструктор общественность. Это запрещает удаление других объектов, кроме B.

  • Для динамически объектов, которые можно вывести класс A из интерфейса с protected виртуального деструктора или именованного самостоятельно уничтожить функцию, которая имеет класс B в качестве друга. B может затем уничтожить любой динамический объект A, выполнив доступ к интерфейсу, к которому он имеет доступ.

Код, который явно называет деструктор, заслуживает того, что он получает.

Помните, что вы никогда не создаете непреодолимую защиту от вредоносного кода, вы просто строите разумное обнаружение и компилируете отчеты о непреднамеренном неправильном использовании.

+1

Целью здесь является то, что класс 'B' должен быть единственным, способным создавать и уничтожать класс' A', но также не должен иметь доступа к переменным и функциям частного члена A. Простите меня, если я не понимаю. Но, по-видимому, я не могу ограничивать доступ к удалению и удалению [] за пределами использования подхода «класс друга», который затем предоставляет все частные члены и функции (которые я бы хотел избежать, если это было возможно). Разве этот подход не просто переместил проблему, я ограничил деструктор от деструктора этими двумя другими операторами? – EncodedNybble

+0

@EncodedNybble: извините, я не думал.варианты с дополнительным аргументом 'operator delete' (как это ни парадоксально) используются только во время динамической инстанцирования, для очистки в случае размещения используется новый, и конструктор бросает. гектометр хорошо, я исправляю ответ. –

+1

И да, не пытаясь действительно защититься от злонамеренности, а просто пытаться предупредить о непреднамеренном использовании (т. Е. «Вы уверены, что хотите сделать или удалить одно из них? Они им действительно не нужны» и «вы конечно, вы хотите называть эти частные функции? Вам не нужно « – EncodedNybble

0

использование shared_ptr

class K{ 
public: 
    int x; 
private: 
    ~K(){}; 
    K(){}; 
private: 
    friend class K_Creater; 
    friend class K_Deleter; 
}; 

struct K_Deleter{ void operator()(K* p) { delete p; } }; 

struct K_Creater{ 
    static shared_ptr<K> Create(){ 
     return shared_ptr<K>(new K, K_Deleter()); 
    } 
}; 



//K* p = new K; prohibited 
shared_ptr<K> p = K_Creator::Create(); 

другой ответ:

#include <iostream> 

class A 
{ 
public: 

    class Key 
    { 
    private: 
     Key(){ 
      std::cout << "Key()" << std::endl; 
     } 
     ~Key(){ 
      std::cout << "~Key()" << std::endl; 
     } 
     friend class B; 
    }; 

    A(Key key){ 
     std::cout << "A(Key key)" << std::endl; 
    } 
    void seti(){ i_=0;} 
private: 
    int i_; 
}; 

class B{ 
public: 
    static void foo(){ 

     A a{A::Key()}; 

     A* pa = new A(A::Key()); 
     delete pa; 

     static A sa({}); 

    } 
}; 

int main(){ 

    B::foo(); 

    //A a{A::Key()}; prohibit 
    //A* pa = new A(A::Key()); prohibit 
    //delete pa; prohibit 
    //static A sa({}); prohibit 

    return 0; 
} 
+0

Использование shared_ptr с «deleter» будет работать, если все указатель. Я бы хотел найти решение, которое позволяет использовать временные/статические/автоматические переменные в стеке, если это вообще возможно. Это лучшее решение, чем функция «destroy», благодарю вас. – EncodedNybble

0

Мое мнение:

Любой класс/функция, которая имеет доступ к конструктору должен также иметь доступ к деструктора.

Вы должны сделать ~A() общественностью с A() является общедоступной. Поскольку ни один другой клиент, кроме B, не может использовать конструктор, в любом случае у них не будет необходимости использовать деструктор.

Вы можете дополнительно ограничить, кто может получить доступ к деструктору, объявив копии и перемещайте конструкторы, а также операторыи delete.

Update

Создание деструктора общественности и объявить прочь копировать и перемещать конструкторы, кажется, решить все ваши проблемы. Вам даже не нужно указывать операторы new и delete или их варианты массивов.

Вот что, я думаю, должно удовлетворить большинство ваших потребностей.

class B; 

class PassKey 
{ 
    private: 
     PassKey() {} 
     ~PassKey() {} 
    friend class B; 
}; 

class A 
{ 
    public: 
     A(PassKey) {} 
     ~A() {} 

    private: 
     // Declare away 
     A(A const&); 
     A(A&&); 
}; 

Теперь давайте посмотрим на то, что B может иметь:

class B 
{ 
    public: 
     B() : a(PassKey()), ap(new A(PassKey())), ap2(new A(PassKey())) {} 
     ~B() { delete ap; } 

     A const& getA() const {return a;} 

     A a; 
     A* ap; 
     std::shared_ptr<A> ap2; 
}; 

Он может иметь следующие типы данных члена:

  1. Объекты типа A.
  2. Ориентировочные указатели по объектам A.
  3. Объекты типа shared_ptr<A>.

Функции-члены B также могут создавать любые из вышеуказанных типов объектов.

Другие классы не могут использовать объекты типа A, так как они не могут его каким-либо образом создать. Все последующие попытки использовать A в различных формах терпят неудачу.

struct C 
{ 
    C() : a(PassKey()) {} // Can't construct an instance of A 
         // since C doesn't have access to 
         // PassKey's constructor. 
    A a; 
}; 

struct D 
{ 
    D() : a(new A(PassKey())) {} // Can't construct an instance of A 
           // since D doesn't have access to 
           // PassKey's constructor. 
    A* a; 
}; 

struct E 
{ 
    E(A const& a) : ap(new A(a)) {} // Can't construct an instance of A 
            // since E doesn't have access to 
            // A's copy constructor. 
    A* ap; 
}; 

class F 
{ 
    public: 
     F(A& a) : ap(new A(std::move(a))) {} // Can't construct an instance of A 
              // since F doesn't have access to 
              // A's move constructor. 
     A* ap; 
}; 
+0

Я согласен, что у них «не будет необходимости», чтобы вызвать деструктор, если они все равно не могут его построить. В первую очередь цель ограничить ctor заключалась в том, чтобы дать пользователям «ножницы безопасности», потому что я им не доверяю. Таким образом, я не доверяю им, чтобы они не удаляли объект, когда им этого не нужно. У меня нет проблем с подходом типа RTFM, просто интересно, есть ли способ немного угасить эти ножницы;) – EncodedNybble

+0

Также A() не является «общедоступным» в обычном смысле. Только классы, которые могут создать LifecycleKey (который оптимизируется компилятором), могут получить доступ к A(). – EncodedNybble

+0

Вы пытаетесь предотвратить непреднамеренное злоупотребление или злонамеренное злоупотребление? –

1

Используйте медиатор-класс:

class mediator; 
class A 
{ 
/* Only for B via mediator */ 
    A(); 
    ~A(); // But how can I get class B to have access to this? 
    friend class mediator; 
/* Past this line the official interface */ 
public: 
    void somePrivateFunction(); 
protected: 
private: 
}; 

class B; 
class mediator 
{ 
    static A* createA() { return new A{}; } 
    static void destroyA(const A* p) { delete p; } 
    // Add additional creators and such here 
    friend class B; 
}; 

Таким образом, только посредник, как часть интерфейса к B, получает полный доступ.

BTW: вместо ограничения доступа к dtor вы можете получить более счастливую перегрузку new и delete и ограничить доступ к ним.
Преимущество: Распределение в стеке вообще возможно, если переменная непосредственно инициализируется без копирования.

void* operator new(std::size_t); 
void* operator new[](std::size_t); 
void operator delete(void*); 
void operator delete[](void*); 
void operator delete(void*, std::size_t) noexcept; 
void operator delete[](void*, std::size_t) noexcept; 
+0

Посмотрите на свое решение, исправьте меня, если я ошибаюсь, но это имеет ту же проблему, что и потенциально просто наличие 'class B' - это друг' class A', разве что «посредник класса» теперь имеет доступ для частных функций и переменных-членов класса A. Посредник действительно * не должен * касаться этих, мой вопрос действительно спрашивает, есть ли способ обеспечить соблюдение на уровне компиляции (не занимая много времени). Хотя он работает концептуально (очень похоже на просто «класс друга класса B»), он просто принимает некоторые проблемы и перемещает их из класса «B» в «посредник класса», не так ли? – EncodedNybble

+0

Ну, вам нужно указать дополнительный интерфейс между 'A' и' B' где-нибудь. Это «где-то» является классом «посредник». См. «Медиатор» не как отдельный реальный класс, а просто как спецификация интерфейса для бэкдора, только «B» может использовать и, таким образом, часть самого 'A'. – Deduplicator

+0

Итак, извините, я неправильно понял ваше решение. Если автор «класса А» также пишет «посредник класса», тогда меньше беспокоиться об открытых частных функциях и членах. В то время как чистое решение по-прежнему не поддерживает ключевое слово delete и новое ключевое слово (с точки зрения класса «B»), и я не уверен, как можно было бы разрушить + dealloc «переменную статического/автоматического/стека» через интерфейс посредника, который будет приятной особенностью. – EncodedNybble

Смежные вопросы