C ++ конструктор вопрос - PullRequest
       17

C ++ конструктор вопрос

5 голосов
/ 14 февраля 2011

В программировании C ++ для абсолютного Новичка, 2-е издание , было следующее утверждение:

HeapPoint::HeapPoint(int x, int y): thePoint(new Point(x,y)) { }

Это равно:

HeapPoint::HeapPoint(int x, int y) { thePoint = new Point(x,y); }

И, поскольку мы делаем это в конструкторе, какие значения назначены для x и y? Должны ли мы записывать значения x и y в new Point(x,y)? Или это правильно?

ОБНОВЛЕНИЕ: Мне кажется, у меня появилась идея инициализации x и y, поскольку в книге в функции есть следующее:

HeapPoint myHeapPoint(2,4);

Ответы [ 6 ]

4 голосов
/ 14 февраля 2011

Как правило, вы должны предпочесть первую конструкцию, т. Е. Использовать список инициализаторов.

Вторая конструкция предпочтительнее, если

  1. Вы хотите поставить попытку .. поймать его или
  2. У вас есть несколько из них, и вы собираетесь хранить их как обычные указатели, и в этом случае вам нужно остерегаться того, что одна из ваших новостей может потерпеть неудачу, и вам потребуется выполнить некоторую очистку. Вы бы справились с этим, используя какой-то тип auto_ptr / unique_ptr, пока не узнаете, что все распределения выполнены успешно, а затем выпустите их. Это потому, что вы предположительно удалили их в своем деструкторе, но ваш деструктор не будет вызван, если конструктор сгенерирует.
2 голосов
/ 14 февраля 2011

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

Когда они не одинаковы?

  1. Если thePoint - это своего рода умный указатель, первая форма инициализирует его, передавая новую точку своему конструктору, тогда как вторая форма, вероятно, инициализирует ее нулем, а затем передает новую точку своему оператору присваивания.
  2. Когда thePoint является одной из нескольких переменных-членов, первая форма инициализирует ее в том порядке, в котором она указана в определении класса, тогда как вторая форма будет присваиваться в теле конструктора. Таким образом, порядок, в котором вещи «гидратируются», может варьироваться между двумя формами, что может иметь значение, если переменные некоторых членов зависят от других.

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

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

1 голос
/ 14 февраля 2011

Эти две формы не совсем одинаковы в том смысле, что список инициализации дает значение для помещения непосредственно в переменную, в то время как переменные инициализируются по умолчанию, если это необходимо (например, std :: strings по умолчанию пусты, но Переменные типа int, double и т. д. имеют фактически случайные значения), а затем перезаписываются при обнаружении операции присваивания. Эта перезапись может быть менее эффективной, но она также может быть недопустимой для некоторых константных и ссылочных переменных-членов, которые нельзя изменять после их создания. В общем, по возможности используйте список инициализаторов, и вы не ошибетесь. Только когда вы обнаружите, что вы еще не можете что-то инициализировать, потому что сначала вам нужно выполнить некоторые другие шаги, вы должны поместить явный оператор присваивания внутри тела конструктора. Помните, что инициализация происходит в том порядке, в котором переменные-члены объявлены в классе, независимо от того, в каком порядке они перечислены в самом конструкторе: хороший компилятор (например, GCC) может предупредить вас об этом.

0 голосов
/ 14 февраля 2011

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

Итак:

  • в вашем первом случае ваш член thePoint напрямую построен с thePoint(new Point(x,y))

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

Если существуют списки инициализации, это по веским причинам (C ++ - очень точный язык, синтаксис действительно строг, за исключением уродливых вещей, происходящих из C, все логично)! Например, если в вашем классе используется ссылочный член, вы должны будете использовать список инициализации, поскольку ваш объект не будет полностью построен:

class A
{
public:
    B& _b;
    C& _c;

    A(B& b, C& c):_b(b), _c(c) // compiles !
    {

    }

    A(B& b, C& c)
    {
        _b(b); // does not compile !
        _c(c); // does not compile !
    }
}

Имейте в виду, что здесь, если бы мы сделали _c(c), _b(b) в первом конструкторе (в обратном порядке), конструкторы копирования классов B и C в любом случае были бы вызваны в одном и том же порядке: они называются в порядке определения членов (т. е. _b до _c), а не в порядке вы пишите их !!!!

0 голосов
/ 14 февраля 2011

Оба одинаковы.Первый случай:

HeapPoint::HeapPoint(int x, int y): thePoint(new Point(x,y)) { }

использует конструктор класса, которому принадлежит thePoint, и указывает на новую выделенную память для Point.

.назначает память, а ее адрес присваивается thePoint

0 голосов
/ 14 февраля 2011

первое не совпадает со вторым.

в данном конкретном случае они , вероятно, приведут к тому же результату. однако , точка может легко реализовать оператор присваивания для new Point и сделать что-то «другое» (у меня нет книги, поэтому я не знаю всех деталей). кроме того, оператор присваивания должен делать то, что вы ожидаете ... однако thePoint может быть контейнером (например, умным указателем), который может (по какой-то странной причине) вести себя иначе при использовании initialization(Point) против default initialization followed by assignment.

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

И, поскольку мы делаем это в конструкторе, какие значения присваиваются для int x и int y?

, который полностью зависит от конструктора Point.

Должны ли мы записывать значения, введенные из x и y в новой точке (x, y)? Или это правильно?

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

HeapPoint::HeapPoint(int x, int y): thePoint(new Point(x,y)) { }

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

const Point* const thePoint;

первый const означает, что вы не можете изменить Точку (например, Point.x или Point.y). Второй констант означает, что вы не можете назначить новое распределение для переменной. тривиальные примеры для примера в OP, но, безусловно, полезные по мере роста ваших программ.

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