Самый интересный вопрос C ++, с которым я недавно столкнулся, выглядит следующим образом:
Мы определили (через профилирование), что наш алгоритм тратит много времени в режиме отладки в MS Visual Studio 2005 с функциями следующего типа:
MyClass f(void)
{
MyClass retval;
// some computation to populate retval
return retval;
}
Как многие из вас, вероятно, знают, возвращаемое здесь вызывает конструктор копирования, чтобы передать копию retval
и затем деструктор на retval
. (Примечание: причина, по которой режим освобождения очень быстрый, потому что это оптимизация возвращаемого значения . Однако мы хотим отключить это при отладке, чтобы мы могли вмешаться и хорошо увидеть вещи в отладчике IDE.)
Итак, один из наших ребят предложил классное (хотя и несколько ошибочное) решение этой проблемы, а именно создание оператора преобразования:
MyClass::MyClass(MyClass *t)
{
// construct "*this" by transferring the contents of *t to *this
// the code goes something like this
this->m_dataPtr = t->m_dataPtr;
// then clear the pointer in *t so that its destruction still works
// but becomes 'trivial'
t->m_dataPtr = 0;
}
, а также изменив указанную выше функцию на:
MyClass f(void)
{
MyClass retval;
// some computation to populate retval
// note the ampersand here which calls the conversion operator just defined
return &retval;
}
Теперь, прежде чем вы съежитесь (что я и делаю, когда пишу это), позвольте мне объяснить обоснование. Идея состоит в том, чтобы создать оператор преобразования, который в основном выполняет «перенос содержимого» во вновь созданную переменную. Экономия происходит потому, что мы больше не делаем глубокую копию, а просто переносим память по ее указателю. Код переходит от 10-минутного времени отладки к 30-секундному времени отладки, что, как вы можете себе представить, оказывает огромное положительное влияние на производительность. Конечно, оптимизация возвращаемого значения лучше работает в режиме деблокирования, но за счет невозможности вмешаться и посмотреть наши переменные.
Конечно, большинство из вас скажут: «Но это злоупотребление оператором конверсии, вы не должны заниматься такими вещами», и я полностью согласен. Вот пример, почему вы не должны делать это тоже (это действительно произошло:)
void BigFunction(void)
{
MyClass *SomeInstance = new MyClass;
// populate SomeInstance somehow
g(SomeInstance);
// some code that uses SomeInstance later
...
}
, где g
определяется как:
void g(MyClass &m)
{
// irrelevant what happens here.
}
Теперь это произошло случайно, то есть человек, который вызвал g()
, не должен был передавать указатель, когда ожидалась ссылка. Тем не менее, не было никакого предупреждения компилятора (конечно). Компилятор точно знал, как конвертировать, и он сделал это. Проблема в том, что вызов g()
будет (потому что мы передали ему MyClass *
, когда он ожидал MyClass &
), вызвал оператор преобразования, что плохо, потому что он установил внутренний указатель в SomeInstance
в 0 и делает SomeInstance
бесполезным для кода, который произошел после вызова g()
. ... и началась отладка.
Итак, мой вопрос: как мы можем получить это ускорение в режиме отладки (который имеет прямое преимущество в отладке) с чистым кодом, который не открывает возможности для того, чтобы такие другие ужасные ошибки проскальзывали через трещины?
Я также собираюсь подсластить банк на этом и предложить свою первую награду на этот, как только он получит право. (50 баллов)