C++ vs Java конструкторы

согласно John C. Mitchell-Concepts в языках программирования,

[...] Java гарантирует, что a конструктор вызывается всякий раз, когда объект создан. [...]

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

Я думаю, что это происходит, когда наследование происходит, но я не могу найти пример для этого случая.

вы знаете какой-нибудь пример?

15 ответов


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

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

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

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

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


в Java есть два случая (я больше не знаю), в которых класс " может быть построен без вызова его конструктора, не приводя к взлому в C или подобном:

  • во время десериализации сериализуемые классы не имеют вызываемого конструктора. Конструктор no-arg наиболее производного несериализуемого класса вызывается механизмом сериализации (в реализации Sun, через не поддающийся проверке байт-код).
  • когда злой Это.

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


для типов C++, которые объявляют конструкторы, невозможно создать экземпляры этих типов без использования конструктора. Например:

class A {
   public:
      A( int x ) : n( x ) {}
  private:
      int n;
};

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


конструкторы Java могут вызывать другой конструктор того же класса. В C++ это невозможно. http://www.parashift.com/c++-faq-lite/ctors.html

POD (простые старые типы данных) не инициализируются с помощью конструкторов в C++:

struct SimpleClass {
    int m_nNumber;
    double m_fAnother;
};

SimpleClass simpleobj = { 0 }; 
SimpleClass simpleobj2 = { 1, 0.5 }; 

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

Если, однако, SimpleClass сам определил конструктор, SimpleClass больше не будет POD, и один из конструкторов всегда будет вызываться.


В C++ при создании экземпляра объекта необходимо вызвать конструктор этого класса.


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

struct X {
   int x;
};
int main() {
   X x;        // implicit default constructor not called
               //    No guarantee in the value of x.x
   X x1 = X(); // creates a temporary, calls its default constructor
               //    and copies that into x1. x1.x is guaranteed to be 0
}

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

для дальнейшего решения вопроса:

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

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

Это, конечно, сделано для совместимости с C.

(как комментирует Нил)

Я думаю, что это происходит, когда наследование происходит, но я не могу привести пример для этого случая.

это не имеет ничего общего с наследованием,но с типом создаваемого объекта.


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

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

метод, который позволяет сделать это не является частью публичного API, он находится в sun.* пакета. Однако, пожалуйста, не говорите мне, что это не часть языка из-за этого. То, что вы можете сделать с public API, собрано поток байтов десериализованного объекта, прочитайте его и там вы идете с экземпляром объекта, конструктор которого никогда не вызывался!


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

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

class A {
   int x;
public:
   A() : X(0) {}
   virtual void f() { x=x+1; }
   virtual int getX() { return x; }
};

int main() {
   A *a = (A *)malloc(sizeof(A));
   cout << a->getX();
   free(a);
}

Это вполне приемлемая программа на C++ и использует непроверенную форму типа бросание, чтобы избежать вызова конструктора. В этом случае x не инициализируется, поэтому можно ожидать непредсказуемого поведения.

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


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

void * operator new ( size_t size )
{

     void *p = malloc(size);

     if(p)
       return p; 
     else
        cout<<endl<<"mem alloc failed";
}

class X
{   

   int X;

};

int main()
{

       X *pX;

       pX = reinterpret_cast<X *>(operator new(sizeof(X)*5)); // no ctor called

}

насколько я помню, Мейерс в своем "эффективном c++" говорит, что объект создается только тогда, когда поток управления достиг конца его конструктора. Иначе это не объект. Всякий раз, когда вы хотите плохо обращаться с некоторой необработанной памятью для фактического объекта, вы можете сделать это:

class SomeClass
{
   int Foo, int Bar;
};

SomeClass* createButNotConstruct()
{
   char * ptrMem = new char[ sizeof(SomeClass) ];
   return reinterpret_cast<SomeClass*>(ptrMem);
}

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


пытается прояснить ситуацию с C++. Множество неточных утверждений в ответах.

В C++ POD и класс ведут себя одинаково. Конструктор всегда вызывается. Для POD конструктор по умолчанию ничего не делает: он не инициализирует значение. Но это ошибка, чтобы сказать, что конструктор не вызывается.

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

class A {
  public: A() {}
};

class B: public A {
  public: B() {}   // Even if not explicitely stated, class A constructor WILL be called!
};

Это, кажется, сводится к определению термина "объект", так что утверждение является тавтологией. В частности, в отношении Java он, по-видимому, определяет "объект" как экземпляр класса. Что касается C++, он (по-видимому) использует более широкое определение объекта, которое включает такие вещи, как примитивные типы, у которых даже нет конструкторов.

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

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

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


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

можно увидеть в следующем фрагменте кода. я еще не видел общего случая, когда конструктор не вызывается, но тогда так много можно увидеть:)

включить

использование пространства имен std;

класс Объект

{

public:

Object ();

~Object ();

};

inline Object:: Object ()

{

cout

};

встроенный объект::~Object ()

{

cout

};

int main ()

{

буфер символов[2 * sizeof (Object)];

объект *объект obj = новый (буферный) объект; / / размещение Новый, 1-й объект

new(buffer + sizeof (Object)) Object; // размещение new, 2nd object

/ / удалить obj; / / не делайте этого

obj[0].~Object(); // уничтожить 1-й объект

obj[1].~Object(); // уничтожить 2-й объект

}


в Java есть некоторые ситуации, когда конструктор не называется.

например, при десериализации класса будет вызываться конструктор по умолчанию первого несериализуемого класса в иерархии типов, но не конструктор текущего класса. Также Object.clone избегает вызова конструктора. Вы также можете создать байт-код и сделать это самостоятельно.

чтобы понять, как это возможно, даже без родной магии кода внутри JRE, просто посмотрите на байт-код Java. Когда new ключевое слово используется в коде Java, из него генерируются две инструкции байт-кода - сначала экземпляр выделяется с new инструкция, а затем конструктор вызывается с помощью invokespecial инструкция.

если вы создаете свой собственный байт-код (например, с ASM), можно изменить invokespecial инструкция для вызова конструктора одного из конструкторов супер классов фактического типа (например,java.lang.Object) или даже полностью пропустите вызов конструктора. СПМ не будет жаловаться на это. (Проверка байт-кода проверяет только то, что каждый конструктор вызывает конструктор своего супер класса, но вызывающий конструктор не проверяется, для которого конструктор вызывается после new.)

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


то, что он имеет в виду, находится в Java, конструктор супер класса всегда вызывается. Это делается путем вызова Super(...), и если вы опустите этот компилятор, он вставит его для вас. Единственное исключение-один конструктор вызывает другой конструктор. В этом случае другой конструктор должен вызвать Super(...).

эта автоматическая вставка кода компилятором на самом деле странная. И если у вас нет super(...) позвоните, и родительский класс не имеет конструктора без параметров приведет в ошибке компиляции. (Странно иметь ошибку компиляции для чего-то, что автоматически вставляется.)

C++ не будет выполнять эту автоматическую вставку для вас.