2010-01-15 4 views
2

У меня есть квест вокруг таких сотрудников.C++: как предотвратить разрушение объектов, построенных в аргументе?

Существует класса А, который имеет объект типа класса B, как это член. Поскольку я хотел бы, чтобы B был базовым классом группы других классов, мне нужно использовать указатель или ссылку на объект, а не его копию, чтобы правильно использовать виртуальные методы B внутри A. Но когда я пишу такой код

class B 
    {public: 
    B(int _i = 1): i(_i) {}; 
    ~B() 
     {i = 0; // just to indicate existence of problem: here maybe something more dangerous, like delete [] operator, as well! 
     cout << "B destructed!\n"; 
     }; 
    virtual int GetI() const {return i;}; // for example 
    protected: 
    int i; 
    }; 

class A 
    {public: 
     A(const B& _b): b(_b) {} 
     void ShowI() {cout <<b.GetI()<<'\n';}; 
    private: 
     const B& b; 
    }; 

и использовать его таким образом

B b(1); 
A a(b); 
a.ShowI(); 

работает отлично:

1 
B destructed! 

Но

A a(B(1)); 
a.ShowI(); 

дают очень нежелательный результат: объект b создает, а ab устанавливается как рефери nce, но только после того, как конструктор A закончен, объект b разрушает! Выход:

B destructed! 
0 

Я повторяю еще раз, что, используя копию вместо ссылки на б в A

class A 
    {public: 
     A(B _b): b(_b) {} 
     void ShowI() {cout <<b.GetI()<<'\n';}; 
    private: 
     B b; 
    }; 

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

Конечно , если B (1) был отправлен функции или методу, а не конструктору класса, он работал идеально. И, конечно, я могу использовать тот же код, что и в this, как описано here, чтобы создать B как правильно клонируемую базу или производный объект, но не оказывается слишком сложно для такой простой ищет проблемы? а что, если я хочу использовать класс B, что я не мог изменить?

ответ

1

Это стандартный вопрос, не бойтесь.

Во-первых, вы можете сохранить свой дизайн с тонким изменением:

class A 
{ 
public: 
    A(B& b): m_b(b) {} 
private: 
    B& m_b; 
}; 

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

Нет никакого (прямого) решения, чтобы фактически сохранить const, так как, к сожалению, компиляторы принимают странную конструкцию &B(), хотя это означает, что вы берете адрес временного (и они даже не стесняются сделать его указателем на не- -const ...).

Существует ряд так называемых умных указателей. Основной, в STL называется std::auto_ptr. Другой (известный) - boost::shared_ptr.

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

Одно предупреждение: не использовать std::auto_ptr. Это зверь, потому что он имеет неестественное поведение в отношении копирования.

std::auto_ptr<A> a(new A()); // Building 
a->myMethod();    // Fine  

std::auto_ptr<A> b = a;  // Constructing b from a 
b->myMethod();    // Fine 
a->myMethod();    // ERROR (and usually crash) 

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

Если у вас есть доступ к следующему стандарту, вы можете использовать std::unique_ptr, как автоматический указатель, за исключением плохого поведения: его нельзя скопировать или присвоить.

В то же время вы можете просто использовать boost::shared_ptr или, возможно, std::tr1::shared_ptr. Они несколько идентичны.

Это прекрасный пример указателей «ссылки подсчитаны». И они в этом сообразительны.

std::vector< boost::shared_ptr<A> > method() 
{ 
    boost::shared_ptr<A> a(new A());  // Create an `A` instance and a pointer to it 
    std::vector< boost::shared_ptr<A> > v; 
    v.push_back(a);       // 2 references to the A instance 
    v.push_back(a);       // 3 references to the A instance 
    return v; 
}           // a is destroyed, only 2 references now 

void function() 
{ 
    std::vector< boost::shared_ptr<A> > w = method(); // 2 instances 
    w.erase(w.begin());        // remove w[0], 1 instance 
}             // w is destroyed, 0 instance 
                // upon dying, destroys A instance 

Это то, что подсчитала ссылка: копия и ее исходная точка в том же экземпляре, и они делят свою собственность. И пока один из них еще жив, экземпляр А существует, будучи уничтоженным последним из них, чтобы умереть, поэтому вам не нужно беспокоиться об этом!

Следует помнить, что они делают share указатель. Если вы измените объект, используя один shared_ptr, все его родственники действительно заметят изменение.Вы можете сделать копию в обычном режиме с указателями:

boost::shared_ptr<A> a(new A()); 
boost::shared_ptr<A> b(new A(*a)); // copies *a into *b, b has its own instance 

Так, чтобы подвести итог:

  • не использовать auto_ptr, вы будете иметь плохие сюрпризы
  • использования unique_ptr если таковые имеются это ваша безопасная ставка и легче иметь дело с
  • использования share_ptr иначе, но остерегайтесь мелководной семантика копирования

Удачи!

0

Standard C++ 8.5.3 Пункт 5

Конструктор, который будет использоваться, чтобы сделать копию, должны быть отозваны ли не копия на самом деле сделано. [Пример:

struct A { }; 
struct B : public A { } b; 
extern B f(); 
const A& rca = f(); // Either bound to the A sub-object of the B rvalue, 
        // or the entire B object is copied and the reference 
        // is bound to the A sub-object of the copy 
—end example] 

Дело в том, что A a(B(1)); очень плохо. struct A получить ссылку на временную.

0

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

std::auto_ptr<B> b(new B()) 
A a(*b); //using overriden operator * to get B& 
5

Временный разрушается, как только инициализация a завершена. И элемент ссылки будет болтаться, ссылаясь на уже не существующий объект.

Решение не сложно или сложно. Полиморфный объект обычно не является временным, но он существует в течение более длительного времени. Таким образом, вы просто используете интеллектуальный указатель для хранения ссылки на переданный объект и создаете объект перед записью с new. Обратите внимание, что для этого класс B должен иметь виртуальный деструктор. shared_ptr будет заботиться об уничтожении объекта, когда ваш родительский объект закончит срок службы. При копировании shared_ptr поделится указанным объектом с копией и оба указывают на тот же объект. Это не обязательно очевидно для пользователей OnwsPolymorphics и не предназначенных для многих случаев использования, так что вы можете сделать его конструктор копирования и оператор присваивания личного:

struct OwnsPolymorphics { 
    explicit OwnsPolymorphics(B *b):p(b) { } 

private: 
    // OwnsPolymorphics is not copyable. 
    OwnsPolymorphics(OwnsPolymorphics const&); 
    OwnsPolymorphics &operator=(OwnsPolymorphics); 

private: 
    boost::shared_ptr<B> p; 
}; 

OwnsPolymorphics owns(new DerivedFromB); 

Или использовать опорный элемент и сохранить объект на стек и передать его вместе с конструктором, как вы показывали.


Удлинение срока службы временного делается только тогда, когда непосредственно присваивание сопзЬ ссылки:

B const& b = DerivedFromB(); 

Тогда вы можете уйти с невиртуальном деструктора и может позвонить виртуальные функции на b - при условии, что они являются функциями-членами-членами. Однако эта специальная обработка не выполняется для членов класса, и она имеет недостаток в требовании создания рабочего экземпляра (полиморфные объекты обычно не копируются или не создаются так - они идентифицируются не по значению, а по идентификатору). Все, что временное другого характера уничтожается после окончания полного выражения, в котором оно появляется, - так происходит для вашего временного объекта B.

+0

Я никогда не слышал о повышении общих указателей до ... Не могу увидеть, должен ли я когда-нибудь вызывать удаление объекта общего указателя p и если эта модель будет работать, если я использую 'DerivedFromB b; OwnsPolymorphics владеет (b); вместе с «OwnsPolymorphics» (новый DerivedFromB); потому что это не будет, если я использую обычный указатель 'B * p;' и «удалить p» в деструкторе. – Nick

+0

Вам не нужно вызывать 'delete'. Умный указатель выполнит эту работу для вас, если ее деструктор вызывается деструктором содержащегося объекта. Он не будет работать для 'DerivedFromB b; OwnsPolymorphics владеет (b); '- этот код, похоже, не имеет большого смысла. Просто сделайте 'OwnsPolymorphics owns (новый DerivedFromB); вместо этого. Там * есть * способы заставить его работать (например, использовать пользовательский делетер с 'shared_ptr'), но все они сделают интерфейс более уродливым. Вы должны четко понимать интерфейс - какова цель этого другого способа строительства? –

+0

Извините, идея моего примера состояла в том, чтобы обе конструкции, упомянутые в вопросе «A a (B (1)); и "B b (1); A a (b);" работает должным образом. Например, я могу написать «double b = 2.0; double c = b ** b; cout << sqrt (b);» а также «double b = 2.0; cout << sqrt (b * b)»; без ошибок. Большое вам спасибо за предложение, но если это невозможно, сделайте обе работающие, я бы предпочел свой вариант без умных указателей. – Nick

2

Когда вы пишете следующее:

A a(B(1)); 
a.ShowI(); 

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

Вы можете использовать std :: auto_ptr (если вы хотите передать семантику собственности, т. Е. Объект, имеющий право собственности на объект B, переданный его конструктору) или иным образом общий указатель (например, Boost shared_ptr).

1

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

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

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

Если вы перешли на указатель вы бы добавить удаление вызова деструктора и ваша конкретизация будет выглядеть следующим образом:

A a(new B(1)); 
+0

Ну, насколько я понимаю, это будет работать в случае вызова «A (новый B()), но не будет работать должным образом, если« B b(); A a (b); потому что деструктор b в этом случае называется дважды: один раз от a и второй от объема существования B B(); – Nick