Подтвердить что ты не робот

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

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

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

Привет

Марк

4b9b3361

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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

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 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 имеет член foo типа pointer to Foo.

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

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

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

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

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

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

Какая точка всего этого?

Существует несколько вариантов использования указателя на элемент:

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

Совет

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

Ответ 3

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

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

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

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

Ответ 4

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

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

Ответ 5

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

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

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

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

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

Ответ 6

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

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

Ответ 7

Некоторые преимущества элемента-указателя:

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

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

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

Ответ 8

Если вы сделаете объект 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 в качестве элемента указателя, поскольку он дает вам возможность указать его на любой другой класс, который получен из него. В основном помогает реализовать динамический полиморфизм.

Ответ 9

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

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

 m_pA = new A();

Кроме того, не забудьте уничтожить объект в деструкторе или у вас есть утечка памяти:

delete m_pA; 
m_pA = NULL;

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

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

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

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

Ответ 10

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

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

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

Ответ 11

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

Однако есть еще некоторые случаи, когда вам нужны указатели. В конце концов, управляемые языки (например, С# или 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 и интеллектуальные указатели (здесь есть много ресурсов и где-то еще в Интернете).