метод, выполняющийся на объекте ДО того, как объект был инициализирован? - PullRequest
1 голос
/ 07 апреля 2009
#include <iostream>
using namespace std;

class Foo
{

public:

 Foo(): initialised(0)
 {
  cout << "Foo() gets called AFTER test() ?!" << endl;
 };

 Foo test()
 {
  cout << "initialised= " << initialised << " ?! - ";
  cout << "but I expect it to be 0 from the 'initialised(0)' initialiser on Foo()" << endl;
  cout << "this method test() is clearly working on an uninitialised object ?!" << endl;
  return Foo();
 }

 ~Foo()
 {};

private:

 int initialised;

};


int main()
{

 //SURE this is bad coding but it compiles and runs
 //I want my class to DETECT and THROW an error to prevent this type of coding
 //in other words how to catch it at run time and throw "not initialised" or something

 Foo foo=foo.test();

}

Ответы [ 4 ]

5 голосов
/ 07 апреля 2009

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

Type *t = (Type*)malloc(sizeof(*t)); 

Какая распространенная идиома в программировании на C и которая до сих пор работает на C ++.

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

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

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

2 голосов
/ 07 апреля 2009

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

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

Foo foo=Foo().test()

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

1 голос
/ 07 апреля 2009

Вы не можете помешать людям плохо кодировать , правда. Он работает так же, как «должен»:

  1. Выделить память для Foo (которая является значением указателя this)
  2. Переход к Foo :: test с помощью: Foo :: test (this), в котором
  3. Он получает значение this-> initialized, которое является случайным мусором, затем оно
  4. Вызывает конструктор Foo по умолчанию (из-за возврата Foo ();), затем
  5. Вызовите конструктор копирования Foo, чтобы скопировать правую Foo ().

Так же, как и должно быть. Вы не можете помешать людям не знать, как правильно использовать C ++.

Лучшее, что вы можете сделать, это иметь магическое число:

class A
{
public:
    A(void) :
    _magicFlag(1337)
    {
    }

    void some_method(void)
    {
        assert (_magicFlag == 1337); /* make sure the constructor has been called */
    }

private:
    unsigned _magicFlag;
}

Это "работает", потому что шансы _magicFlag распределяются там, где значение уже 1337, является низким.

Но на самом деле, не делайте этого.

0 голосов
/ 07 апреля 2009

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

Некоторое время назад DDJ опубликовал статью о простом классе отладки под названием "DogTag" , который можно использовать в качестве средства отладки для помощи:

  • использование объекта после удаления
  • перезапись памяти объекта мусором
  • использование объекта до его инициализации

Я не очень часто его использовал, но он пригодился для встроенного проекта, в котором возникали некоторые ошибки перезаписи памяти.

Это в основном разработка техники "MagicFlag", которую GMan описал .

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