Предотвращение создания экземпляров классов в стеке или в качестве члена данных - PullRequest
0 голосов
/ 19 января 2011

Я начинаю использовать общие указатели в C ++ (Visual Studio 2010) и сталкиваюсь со следующей проблемой.

Я пишу новый модуль, который определяет интерфейс, который сообщает модулю, как вести себя в определенных условиях. Это что-то вроде этого (искусственный пример, чтобы проиллюстрировать мою проблему):

// Interface that should be implemented by user of the module
class RingAlert
   {
   public:
      virtual void ring() = 0;
   };

// Module that does something important
class Module
   {
   public:
      Module (RingAlert &ringAlert) : m_ringAlert(ringAlert) {}
      void dosomething();   // may call RingAlert::ring if something goes wrong.
   private:
      RingAlert &m_ringAlert;
   };

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

typedef std::shared_ptr<RingAlert> RingAlertPtr;

class Module
   {
   public:
      Module (RingAlertPtr ringAlert) : m_ringAlert(ringAlert) {}
      void dosomething();   // may call RingAlert::ring if something goes wrong.
   private:
      RingAlertPtr m_ringAlert;
   };

Теперь пользователи модуля могут создавать новый экземпляр RingAlert и просто передавать его модулю, не сохраняя его где-либо и не удаляя его в конце приложения.

Проблема начинается, если приложение делает что-то вроде этого:

class MyRingAlert : public RingAlert
   {
   public:
      virtual void ring() {std::cout << "ring ring" << std::endl;}
   };

class Application
   {
   public:
   private:
      MyRingAlert m_myRingAlert;
   };

// later, somewhere in application code
Module m(RingAlertPtr(&m_myRingAlert));

В этом примере приложение берет адрес элемента данных и помещает его в общий указатель. Позже в приложении деструктор модуля удалит общий указатель, который уменьшит счетчик ссылок и который затем удалит оповещение о вызове, которое не должно быть удалено, поскольку оно является элементом данных класса Application.

Я нашел способ предотвратить удаление экземпляра указателем с помощью этого (используя лямбда-выражения, но есть и более чистое решение с использованием функции):

Module m(RingAlertPtr(&m_myRingAlert,[](void *){});

Хотя это решает мою проблему, я не совсем доволен этим, потому что это все равно будет создавать проблему, если класс Application будет уничтожен до класса Module.

Единственное хорошее решение, похоже, состоит в том, чтобы заставить остальную часть приложения к новому экземпляру RingAlert (или фактически классу, который реализует RingAlert).

Можно ли это сделать? Есть ли способ предотвратить создание экземпляров кода в подклассах базового класса в стеке или в качестве элемента данных? Другими словами: можем ли мы заставить подклассы базового класса обновляться, если они хотят быть созданы?

Ответы [ 5 ]

3 голосов
/ 19 января 2011

Это в корне ошибочно - что если я хочу, чтобы мой основной класс наследовал и предоставлял этот обратный вызов?Вы искусственно ограничиваете это без реального преимущества.Вместо этого используйте std::function.

class Module {
public:
    Module (std::function<void()> ringAlert) : Alert(ringAlert) {}
    void dosomething() {
        if (something) alert();
    }  
private:
    std::function<void()> alert;
};

Стандартный тип функции чрезвычайно гибок и очень удобен в использовании.

Вы также можете предоставить пользовательскую функцию уничтожения.*

Если я размещаю в стеке, я передаю пустой объект функции.Если я выделяю кучу, я передаю быстрое уничтожение лямбды.

1 голос
/ 19 января 2011

То, что я склонен делать - и это ни в коем случае не правильный путь - или даже лучший способ заключается в следующем:

struct IType
{
    typedef shared_ptr<IType> Ptr;

    virtual DoSomething( ) = 0;

protected:
    virtual ~IType( ) { }
};

class Type : public IType
{
public:
    typedef shared_ptr<Type> Ptr;

    static Ptr New( )
    {
        return Ptr( 
                   new Type( ),
                   &Type::Delete
                  );
    }

private:

    virtual void DoSomething( );

    Type( ); // Defined in the CPP file.

    static void Delete( Type* p )
    {
        delete ( p );
    }
};

Таким образом, вы можете создать только объект типа Type :: Ptr., Вы не можете удалить вложенный тип с помощью метода .get ().

Надеюсь, это поможет.

0 голосов
/ 19 января 2011

Сделайте конструктор для RingAlert закрытым и создайте статический метод, который вызывает new и возвращает указатель (или еще лучше, возвращает shared_ptr).

0 голосов
/ 19 января 2011

Я считаю, что обычное решение - создать конструктор (ы) protected и разрешить создание только через static (или бесплатные friend функции), возвращая shared_ptr<>.Поскольку конструктор защищен, код, не являющийся другом, не может создавать экземпляры, независимо от того, пытается ли этот код создать экземпляр в стеке или в конструкторе как член.

Конечно, вы должны убедиться, что дочерние классы также защищают своих конструкторов.

0 голосов
/ 19 января 2011

А как насчет создания закрытого типа переопределенного оператора переопределений new и delete для класса RingAlert

...