Именованный конструктор и наследование - PullRequest
1 голос
/ 12 февраля 2009

Я работаю над фреймворком C ++ и хотел бы применить автоматическое управление памятью для ряда базовых классов. До сих пор у меня есть стандартный подход, который

class Foo 
{

public:

  static
  shared_ptr<Foo> init() 
  {
    return shared_ptr<Foo>(new Foo);
  }

  ~Foo() 
  {
  }

protected:

  Foo()
  {
  }

};


// Example of use
shared_ptr<Foo> f = Foo::init();

Однако вышеприведенное прерывается, когда я подкласс Foo, поскольку даже если init() наследуется, он все равно возвращает shared_ptr<Foo>, который содержит указатель на экземпляр Foo.

Кто-нибудь может придумать элегантное решение для этого? Должен ли я, возможно, просто придерживаться (полу) ручной упаковки экземпляров класса с shared_ptr? Это также дало бы возможность выставлять параметризованные конструкторы без объявления новых именованных конструкторов ...

Т.е.

template <typename T>
shared_ptr<T> make_shared(T* ptr)
{
  return shared_ptr<T>(ptr)
}

// Example
shared_ptr<T> 
  f1 = make_shared(new Foo()),
  f2 = make_shared(new Foo(1,2));

Ответы [ 8 ]

4 голосов
/ 12 февраля 2009

Я бы попробовал что-то вроде этого:

template<class T>
class creator
{
  public:
    static shared_ptr<T> init()
    {
      return(shared_ptr<T>(new T));
    }
};

class A : public creator<A>
{
};

class B : public A, public creator<B>
{
  public:
    using make_shared<B>::init;
};

// example use
shared_ptr<A> a = A::init();
shared_ptr<B> b = B::init();

Но это не обязательно спасет вас по сравнению с предложенным вами автономным шаблоном.

Редактировать: я пропустил предыдущий ответ, похоже, это та же идея.

3 голосов
/ 12 февраля 2009

Я не понимаю, чего это дает, вы, кажется, не получаете никакого дополнительного управления памятью, используя эту функцию инициализации, чем просто объявив shared_ptr.

int main( void )
{
    shared_ptr<foo> a = foo::init();
    shared_ptr<foo> b( new foo );
}

Какая разница. shared_ptr обеспечивает управление памятью, а не что-либо в init.

2 голосов
/ 12 февраля 2009

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

Но если вы хотите применить этот шаблон, вам нужно повторить его во всех подклассах. Подклассы не могут автоматически «наследовать» init (), так что init () все равно будет вызывать конструктор подкласса, потому что init () не является виртуальным методом и вызывается без объекта.

Я бы оставил конструкторы открытыми как обычно и просто использовал стандарт

shared_ptr<X> x = new X();

Это снижает когнитивную нагрузку, делает ее читабельной и сохраняет гибкость. Так или иначе, мы программируем в нашей компании объекты с подсчетом ссылок.

1 голос
/ 12 февраля 2009

Обычно не стоит форсировать создание объектов, используя shared_ptr, скрывая конструкторы. Я говорю из личного опыта, работая с внутренней библиотекой, которая именно это и сделала. Если вы хотите, чтобы люди всегда переносили свои выделенные объекты, просто убедитесь, что все аргументы и члены, которые хранят экземпляры этих типов, ожидают shared_ptr или weak_ptr вместо голого указателя или ссылки. Вы также можете захотеть получить эти классы из enable_shared_from_this, потому что в системе, где все объекты являются общими, в какой-то момент вам придется передать указатель this на один из методов этих других объектов, и, поскольку они ' Он предназначен только для принятия shared_ptr, вы в довольно плохой форме, если у вашего объекта нет internal_weak_this, чтобы гарантировать, что он не разрушен.

1 голос
/ 12 февраля 2009

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

1 голос
/ 12 февраля 2009

Как насчет ...

template<typename Derived>
class Foo 
{
public:

    static shared_ptr<Derived> init() 
    {
        return shared_ptr<Derived>(new Derived);
    }

    ~Foo() 
    {
    }

protected:

    Foo()
    {
    }

};


class Bar : public Foo<Bar>
{
};

int _tmain(int argc, _TCHAR* argv[])
{
    shared_ptr<Bar> b = Foo<Bar>::init(); 
    return 0;
}
0 голосов
/ 29 декабря 2009

Кстати, в больших C ++-фреймворках принято скрывать «автоматическое управление памятью» от кодера. Это позволяет ему писать более короткий и простой код. Например, в Qt вы можете сделать это:

QPixmap foo() {
    QPixmap pixmap(10, 10);
    return pixmap;
}

void bar() {
    QPixmap a = foo(); // no copying occurs, internal refcount incremented.
    QPixmap b = a;     // ditto.
    QPainter p(&b);
    p.drawPoint(5, 5); // data can no longer be shared, so a copy is made.
    // at this point 'a' is still unchanged!
    p.end();
}

Как и многие вещи в Qt, это имитирует объектную модель Java, но идет дальше, реализуя copy-on-write (которую он называет неявное совместное использование ). Это сделано для того, чтобы сделать поведение API менее удивительным для программистов на C ++, которые не привыкли вызывать clone().

Это реализовано с помощью идиомы d-указателя , которая убивает двух зайцев одним выстрелом - вы обеспечиваете автоматическое управление памятью, и вы изолируете свою реализацию от пользователя ( Pimpl ).

Вы можете посмотреть фактическую реализацию QPixmap здесь: qpixmap.cpp , qpixmap.h .

0 голосов
/ 12 февраля 2009

Вам нужна статическая фабричная функция в каждом типе всей иерархии.

class Foo
{
public:
    static shared_ptr< Foo > instantiate( /* potential arguments */ )
    {
           return shared_ptr< Foo >( new Foo( /* potential arguments */ );
    }

// blah blah blah
};

class Bar : public Foo
{
public:
    static shared_ptr< Bar > instantiate( /* potential arguments */ )
    {
           return shared_ptr< Bar >( new Bar( /* potential arguments */ );
    }

// blah blah blah
};

Если у вас все еще есть путаница, пожалуйста, найдите CppCodeProvider на sourceforge и посмотрите, как это там делается.

...