Члены класса, которые являются объектами-указателями или нет? С++

Если я создаю класс MyClass и у него есть какой-то частный член, скажем MyOtherClass, лучше ли сделать MyOtherClass указателем или нет? Что значит также иметь его как не указатель с точки зрения того, где он хранится в памяти? Будет ли объект создан при создании класса?

Я заметил, что примеры в QT обычно объявляют членов класса указателями, когда они являются классами.

в отношении

Марк

11 ответов


Если я создаю класс MyClass и у него есть какой-то частный член, скажем MyOtherClass, лучше ли сделать MyOtherClass указателем или нет?

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

что означает также иметь его как не указатель с точки зрения того, где он хранится в памяти?

его адрес будет близок к (или равен) this -- gcc (например) имеет некоторые дополнительные параметры для дампа данных класса (размеры, vtables, offsets)

будет ли объект создан при создании класса?

yes-размер MyClass будет расти на sizeof (MyOtherClass) или больше, если компилятор перестроит его (например, к его естественному выравниванию)


где хранится ваш член в памяти?

взгляните на этот пример:

struct Foo { int m; };
struct A {
  Foo foo;
};
struct B {
  Foo *foo;
  B() : foo(new Foo()) { } // ctor: allocate Foo on heap
  ~B() { delete foo; } // dtor: Don't forget this!
};

void bar() {
  A a_stack; // a_stack is on stack
             // a_stack.foo is on stack too
  A* a_heap = new A(); // a_heap is on stack (it's a pointer)
                       // *a_heap (the pointee) is on heap
                       // a_heap->foo is on heap
  B b_stack; // b_stack is on stack
             // b_stack.foo is on stack
             // *b_stack.foo is on heap
  B* b_heap = new B(); // b_heap is on stack
                       // *b_heap is on heap
                       // b_heap->foo is on heap
                       // *(b_heap->foo is on heap
  delete a_heap;
  delete b_heap;
  // B::~B() will delete b_heap->foo!
} 

определим два класса A и B. A сохраняет публичного участника foo типа Foo. B есть типа pointer to Foo.

какова ситуация для A:

  • если вы создаете переменную a_stack типа A на стек, тогда объект (очевидно) и его члены находятся на the стек тоже.
  • если вы создадите указатель на A как a_heap в приведенном выше примере только переменная указателя находится на стек; все остальное (объект и его члены) находятся на кучу.

как выглядит ситуация в случае B:

  • создать B на стек: потом и объект, и его член foo на стек, но объект, что foo указывает на (pointee) находится на кучу. Короче:b_stack.foo (указатель) находится в стеке, но *b_stack.foo (pointee)находится в куче.
  • вы создаете указатель на B имени b_heap: b_heap (указатель) находится в стеке, *b_heap (том ссылающиеся на заданный) на кучу, а также и *b_heap->foo.

будет ли объект автоматически создан?

  • в случае A: да,foo будет автоматически создан путем вызова неявного конструктора по умолчанию Foo. Это создаст integer но не intitialize его (он будет иметь случайное число)!
  • в случае B: если вы опустите наш ctor и dtor, то foo (указатель) также будет создан и инициализирован случайным числом, что означает, что он будет указывать на случайном месте в куче. Но обратите внимание, что указатель существует! Обратите внимание также, что неявный конструктор по умолчанию не будет выделять что-то для foo для вас, вы должны сделать это явно. Вот почему вам обычно нужен явный конструктор и сопровождающих деструктор для выделения и удаления указателя вашего члена. Не забывайте о скопировать семантику: что произойдет с указателем, если вы скопируете объект (через конструкцию копирования или задание)?

в чем смысл всего этого?

существует несколько случаев использования указателя на член:

  • чтобы указать на объект, которым вы не владеете. Предположим, вашему классу нужен доступ к огромной структуре данных, которую очень дорого копировать. Затем вы можете просто сохранить указатель на эту структуру данных. Имейте в виду, что в этом случае создание и удаление структуры данных выходит за рамки ваш класс. Кто-то другой должен позаботиться.
  • увеличение времени компиляции, так как в вашем заголовочном файле не обязательно указывать указатель.
  • немного более продвинутый; когда ваш класс имеет указатель на другой класс, который хранит все частные члены, "идиома Pimpl":http://c2.com/cgi/wiki?PimplIdiom, взгляните также на Саттера, H. (2000): Исключительный C++, p. 99--119
  • и некоторые другие, посмотрите на другие ответы

советы

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


в C++ указатели являются объектами сами по себе. Они на самом деле не привязаны к тому, на что они указывают, и нет никакого специального взаимодействия между указателем и его указателем (это слово?)

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

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

однако, если ваш класс знает о другой объект, тогда указатель может быть хорошим способом его представления (так как несколько экземпляров вашего класса могут указывать на один и тот же экземпляр, не принимая на себя ответственность за него и не контролируя его время жизни)


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

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


этот вопрос можно было бы обсуждать бесконечно, но основы таковы:

если MyOtherClass не является указателем:

  • создание и уничтожение MyOtherClass происходит автоматически, что может уменьшить количество ошибок.
  • память, используемая MyOtherClass, локальна для MyClassInstance, что может повысить производительность.

если MyOtherClass является указателем:

  • создание и разрушение MyOtherClass-это ваша ответственность
  • MyOtherClass может быть NULL, которые могут иметь значение в вашем контексте и может сохранить память
  • два экземпляра MyClass могут использовать один и тот же MyOtherClass

Я следую следующему правилу: если объект-член живет и умирает с инкапсулирующим объектом, не используйте указатели. Вам понадобится указатель, если объект-член должен пережить инкапсулирующий объект по какой-либо причине. Зависит от поставленной задачи.

обычно вы используете указатель, если объект-член задан вам, а не создан вами. Затем вы обычно не должны уничтожить.


некоторые преимущества члена указатель:

  • дочерний объект (MyOtherClass) может иметь другое время жизни, чем его Родительский (MyClass).
  • объект может быть разделен между несколькими объектами MyClass (или другими).
  • при компиляции файла заголовка для MyClass компилятору необязательно знать определение MyOtherClass. Вам не нужно включать его заголовок, тем самым уменьшая время компиляции.
  • делает размер MyClass меньший. Это может быть важно для производительности, если ваш код выполняет много копирования объектов MyClass. Вы можете просто скопировать указатель MyOtherClass и реализовать какую-то систему подсчета ссылок.

преимущества наличия члена в качестве объекта:

  • вам не нужно явно писать код для создания и уничтожения объекта. Это проще и менее подвержено ошибкам.
  • делает управление памятью более эффективным, потому что только один блок памяти нужно выделить вместо двух.
  • реализация операторов присваивания, конструкторов копирования / перемещения и т. д. намного проще.
  • более интуитивным

Если вы делаете объект MyOtherClass членом вашего MyClass:

size of MyClass = size of MyClass + size of MyOtherClass

Если вы сделаете объект MyOtherClass членом указателя MyClass:

size of MyClass = size of MyClass + size of any pointer on your system

Вы можете сохранить MyOtherClass в качестве члена указателя, потому что это дает вам гибкость, чтобы указать его на любой другой класс, производный от него. В основном помогает реализовать полиморфизм dynamice.


Это зависит... :-)

если вы используете указатели, чтобы сказать class A, вы должны создать объект типа A, например, в конструкторе вашего класса

 m_pA = new A();
не забудьте уничтожить объект в деструкторе или у вас есть утечка памяти:
delete m_pA; 
m_pA = NULL;

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

С другой стороны, наличие указателя имеет следующие преимущества:

  • если ваш объект выделен на стека и использует много памяти это не будет выделено из стек, но из кучи.

  • вы можете построить свой объект позже (например, в методе Create) или уничтожить его раньше (в метод Close)


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

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

когда вы используете auto_ptr, вам нужно только позаботиться о строительстве, которое вы обычно можете сделать в списке инициализаторов. Разрушения вместе с родительским объектом гарантируется auto_ptr.


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

однако есть еще несколько случаев, когда вам нужны указатели. В конце концов, управляемые языки (например, C# или Java) фактически содержат объекты-члены указателями.

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

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

struct Foo
{
    Foo() 
    {
        bar_ = new Bar();
        baz_ = new Baz(); // If this line throw, bar_ is never reclaimed
                          // See copy constructor for a workaround
    }

    Foo(Foo const& x)
    {
        bar_ = x.bar_.clone();
        try { baz_ = x.baz_.clone(); }
        catch (...) { delete bar_; throw; }
    }

    // Copy and swap idiom is perfect for this.
    // It yields exception safe operator= if the copy constructor
    // is exception safe.
    void swap(Foo& x) throw()
    { std::swap(bar_, x.bar_); std::swap(baz_, x.baz_); }

    Foo& operator=(Foo x) { x.swap(*this); return *this; }

private:
    Bar* bar_;
    Baz* baz_;
};

Как вы видите, довольно громоздко иметь безопасные конструкторы исключений в присутствии указателей. Вы должны посмотреть на RAII и умные указатели (есть много ресурсы здесь и где-то еще в интернете).