Каких ловушек C++ мне следует избегать?

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

Есть ли другие распространенные ошибки, которых следует избегать в C++?

Я думал, что C++ является - ловушка, которую следует избегать.

Cheery 22.11.2008 22:07

Интересно читать ответы в свете профессионального опыта работы со встроенными системами. (Даже если указанные встроенные системы имеют много процессоров и тонну памяти.)

dash-tom-bang 14.05.2010 23:12

Это риторический вопрос, используемый для инициирования обсуждения, который соответствует критериям причины «не настоящий вопрос» и кажется более подходящим для вашего блога или сайта посвященный обсуждению.

Roger Pate 13.07.2010 10:24

Эээ .. все они ..?

bobobobo 13.07.2010 16:10
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
74
4
16 789
29
Перейти к ответу Данный вопрос помечен как решенный

Ответы 29

Книга Проблемы с C++ может оказаться полезной.

Веб-страница Подводные камни C++ Скотта Уиллера описывает некоторые из основных ошибок C++.

У некоторых должны быть книги по C++, которые помогут избежать распространенных ошибок C++:

Эффективный C++
Более эффективный C++
Эффективный STL

Книга Effective STL объясняет вектор проблемы bools :)

Ответ принят как подходящий

Краткий список может быть:

  • Избегайте утечек памяти за счет использования общих указателей для управления выделением и очисткой памяти.
  • Используйте идиому Приобретение ресурсов - это инициализация (RAII) для управления очисткой ресурсов - особенно при наличии исключений
  • Избегайте вызова виртуальных функций в конструкторах
  • По возможности используйте минималистские методы кодирования - например, объявление переменных только при необходимости, определение переменных и раннее проектирование, где это возможно.
  • По-настоящему понимайте обработку исключений в вашем коде - как в отношении исключений, которые вы выбрасываете, так и в отношении тех, которые создаются классами, которые вы можете использовать косвенно. Это особенно важно при наличии шаблонов.

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

Вот несколько отличных книг по этой теме:

  • Эффективный C++ - Скотт Мейерс
  • Более эффективный C++ - Скотт Мейерс
  • Стандарты кодирования C++ - Саттер и Александреску
  • Часто задаваемые вопросы по C++ - Cline

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

вы указали правильные и лучшие книги, которые я искал. :)

Namratha Patil 11.12.2008 08:20

«Избегайте вызова виртуальных функций в конструкторах»

Billy ONeal 30.03.2010 02:43

Может быть, включить виртуальные деструкторы и как правильно улавливать (и повторно генерировать) исключения?

Asgeir S. Nilsen 30.03.2010 02:44

@BillyONeal, я бы, наверное, оставил это «избегать». Но в любом случае поведение для виртуальных вызовов в конструкторах четко определено. Такой вызов не является неопределенным поведением, за исключением случаев, когда вызов происходит с чистой виртуальной функцией из конструктора чистого виртуального класса (и аналогично для деструкторов)

Johannes Schaub - litb 31.03.2010 01:11

Еще одна замечательная книга Мейерса - это, конечно же, «Эффективный STL»! Можно также добавить, поскольку он сейчас отсутствует, Effective Modern C++

aho 13.11.2015 15:12

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

Проверьте boost.org. Он предоставляет множество дополнительных функций, особенно их реализации интеллектуального указателя.

Я уже упоминал об этом несколько раз, но книги Скотта Мейерса Эффективный C++ и Эффективный STL действительно на вес золота за помощь в C++.

Если подумать, Проблемы с C++ Стивена Дьюхерста также является отличным ресурсом «из окопов». Его пункт о создании собственных исключений и о том, как они должны быть сконструированы, действительно помог мне в одном проекте.

Две ошибки, которые я хотел бы не усвоить на собственном горьком опыте:

(1) Большая часть вывода (например, printf) по умолчанию буферизуется. Если вы отлаживаете аварийный код и используете буферизованные операторы отладки, последний вывод, который вы видите, может действительно быть последним оператором печати, встреченным в коде. Решение состоит в том, чтобы очищать буфер после каждой отладочной печати (или вообще отключать буферизацию).

(2) Будьте осторожны с инициализацией - (a) избегайте экземпляров классов как глобальных объектов / статик; и (б) попытаться инициализировать все ваши переменные-члены некоторым безопасным значением в ctor, даже если это тривиальное значение, такое как NULL для указателей.

Обоснование: порядок инициализации глобального объекта не гарантируется (глобальные переменные включают статические переменные), поэтому вы можете получить код, который кажется недетерминированным, поскольку он зависит от инициализации объекта X до объекта Y. Если вы явно не инициализируете объект переменная примитивного типа, такая как член bool или enum класса, вы получите разные значения в неожиданных ситуациях - опять же, поведение может показаться очень недетерминированным.

решение не в том, чтобы отлаживать отпечатки

Dustin Getz 16.11.2008 00:59

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

xan 17.11.2008 15:48

Определенно есть более изощренные способы отладки. Но использование распечаток проверено и верно, и работает в гораздо большем количестве мест, чем у вас может быть доступ к хорошему отладчику. Я не единственный, кто так думает - см., Например, книгу «Практика программирования» Пайка и Керниган.

Tyler 04.12.2008 21:32

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

j_random_hacker 30.08.2009 14:34

printf (и std :: cout) часто буферизируются только по строке, поэтому, если вы относительно уверены, что не происходит сбой между запуском printf и переходом на новую строку, все должно быть в порядке. Также обратите внимание на ошибки компилятора, которые не позволяют генерировать символы отладки <grumble grumble>

dash-tom-bang 14.05.2010 23:10

PRQA имеет отличный и бесплатный стандарт кодирования C++, основанный на книгах Скотта Мейерса, Бьярна Страустропа и Херба Саттера. Он объединяет всю эту информацию в одном документе.

Вот несколько ям, в которые мне посчастливилось попасть. У всего этого есть веские причины, которые я понял только после того, как меня укусило поведение, которое меня удивило.

  • virtual функционирует в конструкторах не.

  • Не нарушайте ODR (правило одного определения), для этого и нужны анонимные пространства имен (среди прочего).

  • Порядок инициализации членов зависит от порядка, в котором они объявлены.

    class bar {
        vector<int> vec_;
        unsigned size_; // Note size_ declared *after* vec_
    public:
        bar(unsigned size)
            : size_(size)
            , vec_(size_) // size_ is uninitialized
            {}
    };
    
  • Значения по умолчанию и virtual имеют разную семантику.

    class base {
    public:
        virtual foo(int i = 42) { cout << "base " << i; }
    };
    
    class derived : public base {
    public:
        virtual foo(int i = 12) { cout << "derived "<< i; }
    };
    
    derived d;
    base& b = d;
    b.foo(); // Outputs `derived 42`
    

Последний - непростой! Ой!

j_random_hacker 30.08.2009 14:36

C# делает то же самое (как виртуальное значение / значение по умолчанию), теперь, когда C# 4 имеет значения по умолчанию.

BlueRaja - Danny Pflughoeft 14.05.2010 23:02

Будьте осторожны при использовании интеллектуальных указателей и контейнерных классов.

Вопрос к ответу: что плохого в использовании интеллектуальных указателей с классами контейнеров? например: vector>. Вы можете уточнить?

Aaron 17.09.2008 09:23

он имеет в виду контейнеры auto_ptr, что запрещено, но иногда компилируется

Dustin Getz 16.11.2008 01:00

@Aaron: в частности, оператор присваивания auto_ptr уничтожает его исходный операнд, что означает, что он не может использоваться со стандартными контейнерами, которые полагаются на то, что этого не происходит. Однако shared_ptr в порядке.

j_random_hacker 30.08.2009 14:38

У Брайана отличный список: я бы добавил: «Всегда явно отмечать конструкторы с одним аргументом (кроме тех редких случаев, когда требуется автоматическое приведение)».

Не совсем конкретный совет, а общее руководство: проверьте свои источники. C++ - старый язык, и с годами он сильно изменился. Лучшие практики изменились вместе с ним, но, к сожалению, есть еще много старой информации. Здесь было несколько очень хороших рекомендаций по книгам - я могу купить каждую книгу Скотта Мейерса по C++. Ознакомьтесь с Boost и стилями кодирования, используемыми в Boost - люди, участвующие в этом проекте, находятся на переднем крае дизайна C++.

Не изобретайте велосипед. Ознакомьтесь с STL и Boost и используйте их возможности, когда это возможно, катаясь самостоятельно. В частности, используйте строки и коллекции STL, если у вас нет очень и очень веской причины не делать этого. Познакомьтесь с auto_ptr и библиотекой интеллектуальных указателей Boost очень хорошо, поймите, при каких обстоятельствах предполагается использовать каждый тип интеллектуального указателя, а затем используйте интеллектуальные указатели везде, где в противном случае вы могли бы использовать необработанные указатели. Ваш код будет столь же эффективным и гораздо менее подвержен утечкам памяти.

Используйте static_cast, dynamic_cast, const_cast и reinterpret_cast вместо приведений в стиле C. В отличие от приведений в стиле C, они сообщат вам, действительно ли вы запрашиваете другой тип приведения, чем вы думаете. И они визуально выделяются, предупреждая читателя о том, что проводится гипсовая повязка.

Избегайте псевдоклассы и квазиклассы ... Перепроектировать в основном.

В настоящее время я работаю над таким проектом с (n) квазиклассами. Нам нужно больше осознавать этот антипаттерн!

DarenW 14.07.2010 05:26
  1. Не читая C++ FAQ Lite. Это объясняет многие плохие (и хорошие!) Практики.
  2. Без использования Способствовать росту. Вы избавите себя от многих разочарований, воспользовавшись функцией Boost, где это возможно.

Использование C++, например C. Наличие цикла создания и выпуска в коде.

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

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

// C Code
void myFunc()
{
    Plop*   plop = createMyPlopResource();

    // Use the plop

    releaseMyPlopResource(plop);
}

В C++ это должно быть заключено в объект:

// C++
class PlopResource
{
    public:
        PlopResource()
        {
            mPlop=createMyPlopResource();
            // handle exceptions and errors.
        }
        ~PlopResource()
        {
             releaseMyPlopResource(mPlop);
        }
    private:
        Plop*  mPlop;
 };

void myFunc()
{
    PlopResource  plop;

    // Use the plop
    // Exception safe release on exit.
}

я не уверен, стоит ли нам его добавлять. но, может быть, нам стоит сделать его не копируемым / непередаваемым?

Johannes Schaub - litb 11.11.2008 20:20

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

class SomeClass
{
    ...
    void DoSomething()
    {
        ++counter;    // crash here!
    }
    int counter;
};

void Foo(SomeClass & ref)
{
    ...
    ref.DoSomething();    // if DoSomething is virtual, you might crash here
    ...
}

void Bar(SomeClass * ptr)
{
    Foo(*ptr);    // if ptr is NULL, you have created an invalid reference
                  // which probably WILL NOT crash here
}

Проверка на NULL не очень помогает. Указатель может иметь ненулевое значение и по-прежнему указывать на удаленный или иным образом недопустимый объект.

Nemanja Trifunovic 11.11.2008 23:46

Верно, но по моему опыту, указатель NULL встречается чаще, чем другие типы недопустимых указателей. Может быть, это потому, что у меня есть привычка обнулять свои указатели после их удаления.

Mark Ransom 12.11.2008 02:30

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

xtofl 12.11.2008 22:45

Намерение (x == 10):

if (x = 10) {
    //Do something
}

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

Практически любой компилятор в наши дни выдаст предупреждение об этом

Adam Rosenfield 11.11.2008 23:39

выполнение константы == для переменной поможет обнаружить эти ошибки, скажем, if (10 = x), компилятор выдаст ошибку на этом

PiNoYBoY82 12.11.2008 08:41

Это не поможет, если вы намеревались использовать if (x == y)

dan04 05.07.2010 03:04

Сохраняйте четкость пространств имен (включая структуру, класс, пространство имен и использование). Это мое разочарование номер один, когда программа просто не компилируется.

Чтобы напортачить, часто используйте прямые указатели. Вместо этого используйте RAII практически для всего, конечно, убедитесь, что вы используете правильные интеллектуальные указатели. Если вы пишете «удалить» где-нибудь за пределами дескриптора или класса указателя, скорее всего, вы делаете это неправильно.

  • Близпаста. Я очень часто вижу это ...

  • Неинициализированные переменные - огромная ошибка, которую совершают мои ученики. Многие люди, занимающиеся Java, забывают, что простая фраза "int counter" не устанавливает счетчик в 0. Поскольку вам нужно определить переменные в h-файле (и инициализировать их в конструкторе / настройке объекта), об этом легко забыть.

  • Поочередные ошибки при доступе к петлям / массиву for.

  • Некорректная очистка объектного кода при запуске voodoo.

  • static_cast downcast on a virtual base class

Не совсем ... Теперь о моем заблуждении: я думал, что A в дальнейшем был виртуальным базовым классом, хотя на самом деле это не так; это, согласно 10.3.1, полиморфный класс. Использование static_cast здесь вроде бы нормально.

struct B { virtual ~B() {} };

struct D : B { };

В общем, да, это опасная ловушка.

см. мой расширенный вопрос выше

Johannes Schaub - litb 12.11.2008 08:01

#include <boost/shared_ptr.hpp>
class A {
public:
  void nuke() {
     boost::shared_ptr<A> (this);
  }
};

int main(int argc, char** argv) {
  A a;
  a.nuke();
  return(0);
}

Неспособность использовать boost :: shared_ptr вряд ли является ловушкой языка.

0xC0DEFACE 27.06.2009 05:55

+1. Хотя в документах shared_ptr указано, что это использование не поддерживается (и предлагается обходной путь, enable_shared_from_this), это общий вариант использования, и не сразу очевидно, что приведенный выше код завершится ошибкой. Кажется, он даже действует по правилу «немедленно обернуть любой необработанный указатель в shared_ptr». Настоящая ловушка ИМХО.

j_random_hacker 30.08.2009 14:49

Забыв определить деструктор базового класса virtual. Это означает, что вызов delete на базе * не приведет к разрушению производной части.

Подводные камни в порядке убывания их важности

Прежде всего, вам следует посетить отмеченный наградами C++ FAQ. В нем есть много хороших ответов на подводные камни. Если у вас есть дополнительные вопросы, посетите ##c++ на irc.freenode.org в IRC. Мы рады помочь вам, если сможем. Обратите внимание, что все следующие подводные камни изначально написаны. Их не просто копируют из случайных источников.


delete[] on new, delete on new[]

Решение: Выполнение вышеуказанного приводит к неопределенному поведению: все может случиться. Поймите свой код и то, что он делает, и всегда delete[] то, что вы new[], и delete, что вы new, тогда этого не произойдет.

Исключение:

typedef T type[N]; T * pT = new type; delete[] pT;

Вам нужен delete[], даже если вы new, поскольку вы недавно создали массив. Поэтому, если вы работаете с typedef, будьте особенно осторожны.


Calling a virtual function in a constructor or destructor

Решение: вызов виртуальной функции не вызовет замещающие функции в производных классах. Вызов чистая виртуальная функция в конструкторе или десктрукторе является неопределенным поведением.


Calling delete or delete[] on an already deleted pointer

Решение: присвоить 0 каждому удаляемому указателю. Вызов delete или delete[] по нулевому указателю ничего не делает.


Taking the sizeof of a pointer, when the number of elements of an 'array' is to be calculated.

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


Using an array as if it were a pointer. Thus, using T ** for a two dimentional array.

Решение: См. здесь, почему они разные и как вы с ними справляетесь.


Writing to a string literal: char * c = "hello"; *c = 'B';

Решение: выделите массив, который инициализируется данными строкового литерала, затем вы можете записать в него:

char c[] = "hello"; *c = 'B';

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


Creating resources, then forgetting to free them when something throws.

Решение: используйте умные указатели, такие как std::unique_ptr или std::shared_ptr, как указано в других ответах.


Modifying an object twice like in this example: i = ++i;

Решение: Вышеупомянутое должно было присвоить i значение i+1. Но что он делает, не определено. Вместо увеличения i и присвоения результата он также изменяет i с правой стороны. Изменение объекта между двумя точками следования - неопределенное поведение. Точки последовательности включают ||, &&, comma-operator, semicolon и entering a function (неполный список!). Измените код на следующий, чтобы он работал правильно: i = i + 1;


Разные проблемы

Forgetting to flush streams before calling a blocking function like sleep.

Решение: очистить поток, передавая либо std::endl вместо \n, либо вызывая stream.flush();.


Declaring a function instead of a variable.

Решение: проблема возникает из-за того, что компилятор интерпретирует, например,

Type t(other_type(value));

как объявление функции функции t, возвращающей Type и имеющей параметр типа other_type, который называется value. Вы решаете это, заключая скобки вокруг первого аргумента. Теперь у вас есть переменная t типа Type:

Type t((other_type(value)));

Calling the function of a free object that is only declared in the current translation unit (.cpp file).

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

House & getTheHouse() { static House h; return h; }

Это создало бы объект по запросу и оставило бы вас с полностью сконструированным объектом во время вызова функций на нем.


Defining a template in a .cpp file, while it's used in a different .cpp file.

Решение: Почти всегда вы будете получать такие ошибки, как undefined reference to .... Поместите все определения шаблонов в заголовок, чтобы, когда компилятор их использует, он уже мог создать необходимый код.


static_cast<Derived*>(base); if base is a pointer to a virtual base class of Derived.

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


dynamic_cast<Derived*>(ptr_to_base); if base is non-polymorphic

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


Making your function accept T const **

Решение: Вы можете подумать, что это безопаснее, чем использование T **, но на самом деле это вызовет головную боль у людей, которые хотят пройти T**: стандарт этого не позволяет. Это наглядный пример того, почему это запрещено:

int main() {
    char const c = ’c’;
    char* pc;
    char const** pcc = &pc; //1: not allowed
    *pcc = &c;
    *pc = ’C’; //2: modifies a const object
}

Вместо этого всегда принимайте T const* const*;.

Еще одна (закрытая) ветка о подводных камнях C++, поэтому люди, которые их ищут, найдут их - это вопрос о переполнении стека Подводные камни C++.

а [я] = ++ я; // чтение измененной переменной дважды приводит к неопределенному поведению ... вы также можете добавить это, если хотите

yesraaj 29.08.2009 19:18

+1, много хороших моментов. Тот, о смешивании typedef и delete [], был для меня совершенно новым! Еще один угловой случай, который стоит запомнить ... :(

j_random_hacker 30.08.2009 14:32

«Назначьте 0 каждому удаляемому указателю».

Billy ONeal 30.03.2010 02:45

@BillyONeal, вы не можете определить, удалили ли вы указатель уже, если не установили для него значение null после его удаления. Это не обязательно ошибка, которую нужно удалить дважды, если вы просто установите для нее значение null после этого, поэтому мое предлагаемое решение.

Johannes Schaub - litb 30.03.2010 09:18

@Johannes Schaub - litb: Верно, но я считаю, что это не надежно. Если у кого-то есть копия указателя и он пытается удалить ее, у вас все равно возникают проблемы с двойным освобождением.

Billy ONeal 30.03.2010 16:27

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

Johannes Schaub - litb 31.03.2010 00:58

«Еще одна (закрытая) ветка о подводных камнях C++, так что люди, которые их ищут, найдут их, - это вопрос о переполнении стека Подводные камни C++». - на данный момент ссылка не работает.

sompylasar 05.12.2017 21:54

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

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

Забыть & и тем самым создать копию вместо ссылки.

Это случилось со мной дважды по-разному:

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

  • Я забыл & в переменной экземпляра, в результате чего объект был скопирован. После регистрации в качестве слушателя копии я задумался, почему я никогда не получал обратные вызовы от исходного объекта.

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

Другие вопросы по теме