Опасайтесь неявных преобразований в С++


Перевод с блога The Old New Thing. Оригинал здесь.

Темой для сегодняшний разговор послужил вопрос клиента:

"Я пытаюсь устранить ошибку, связанную с переполненим стека. Чтобы уменьшить размер стекового фрейма я удалил все локальные переменные, какие только мог, но все равно фрейм остается слишком большим и я не могу понять, где выделяется эта память. Что еще находится в стеке кроме локальных переменных, параметров, сохраненных регистров, и адреса возврата? Да, есть еще данные для структурной обработки исключений (SEH), но они обычно не занимают так много памяти и поэтому не могут быть причиной такого таинственного потребления стека…"

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

class BigBuffer
{
public:
    BigBuffer(int initialValue)
    { memset(buffer, initialValue, sizeof(buffer)); }
private:
    char buffer[65536];
};

extern void Foo(const BigBuffer& o);

void oops()
{
    Foo(3);
}

"Разве это вообще скомпилируется? Функция Foo ожидает BigBuffer, а не int!" Да, это скомпилируется.

Это произойдет потому что компилятор использует клнструктор класса BigBuffer как конвертер. Другими словами, компилятор добавлет вот такую временную переменную:

void oops()
{
    BigBuffer temp(3);
    Foo(temp);
}

Он это делает, потому что конструктор, который принимает ровно один параметр может использоваться двояко:
как обычный конструктор (что мы видим в случае с BigBuffer temp(3) ) или для того, чтобы обеспечить
неявное преобразование типа аргумента в конструируемый тип. В этом случае конструктор BigBuffer(int) используется для преобразования типа int в тип BigBuffer.

Чтобы этого избежать следует использовать ключевое слово explicit.

class BigBuffer
{
public:
    explicit BigBuffer(int initialValue)
    { memset(buffer, initialValue, sizeof(buffer)); }
private:
    char buffer[65536];
};

Теперь, вызов Foo(3) приведет к ошибке компиляции:

sample.cpp: error C2664: ‘Foo’ : cannot convert parameter 1 from
‘int’ to ‘const BigBuffer &’
Reason: cannot convert from ‘int’ to ‘const BigBuffer’
Constructor for class ‘BigBuffer’ is declared ‘explicit’

Реклама
Запись опубликована в рубрике Uncategorized. Добавьте в закладки постоянную ссылку.

Добавить комментарий

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

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s