Запрещение создания временных объектов

при отладке сбоя в многопоточном приложении я, наконец, нашел проблему в этом заявлении:

CSingleLock(&m_criticalSection, TRUE);

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

7 ответов


Edit: как отмечает j_random_hacker, можно заставить пользователя объявить именованный объект, чтобы вынуть блокировку.

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

// take out a lock:
if (m_multiThreaded)
{
    CSingleLock c(&m_criticalSection, TRUE);
}

// do other stuff, assuming lock is held

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

еще одна вероятная ошибка:

 CSingleLock *c = new CSingleLock(&m_criticalSection, TRUE);

 // do other stuff, don't call delete on c...

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

В C++0x будет другой способ сделать все это, используя lambdas. Определите функцию:

template <class TLock, class TLockedOperation>
void WithLock(TLock *lock, const TLockedOperation &op)
{
    CSingleLock c(lock, TRUE);
    op();
}

эта функция фиксирует правильное использование CSingleLock. Теперь пусть пользователи делают это:

WithLock(&m_criticalSection, 
[&] {
        // do stuff, lock is held in this context.
    });

это намного сложнее для пользователя завинчивать. Сначала синтаксис выглядит странно, но [ & ], за которым следует блок кода, означает " определить функцию, которая не принимает args, и если я ссылаюсь на что-либо по имени, и это имя чего-то внешнего (например, локальной переменной в содержащей функции), позвольте мне получить к ней доступ по неконст-ссылке, чтобы я мог ее изменить.)


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

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

class CSingleLock {
    friend void Lock(CSingleLock& lock) {
        // Perform the actual locking here.
    }
};

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

использование:

CSingleLock myLock(&m_criticalSection, TRUE);
Lock(myLock);

Да, это немного более громоздко писать. Но теперь, компилятор будет жаловаться, если вы попробуете:

Lock(CSingleLock(&m_criticalSection, TRUE));   // Error! Caught at compile time.

потому что параметр non-const ref Lock() невозможно привязать к временному.

удивительно, но методы класса можете работы на временных ... вот почему Lock() должна быть свободной функцией. Если вы уроните friend спецификатор и параметр функции в верхнем фрагменте, чтобы сделать Lock() метод, тогда компилятор с радостью позволит вам написать:

CSingleLock(&m_criticalSection, TRUE).Lock();  // Yikes!

MS КОМПИЛЯТОР ПРИМЕЧАНИЕ: MSVC++ версии до Visual Studio .NET 2003 неправильно разрешены функции для привязки к ссылкам без const в версиях до VC++ 2005. такое поведение было фиксированной в VC++ 2005 и выше.


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


Я так не думаю.

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


компилятор не должен запрещать создание временных объектов, IMHO.

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

std::vector<T>(v).swap(v);

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

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

CSingleLock aLock(&m_criticalSection); //Don't use the second parameter whose default is FALSE

aLock.Lock();  //an explicit lock should take care of your problem

как насчет следующего? Немного злоупотребляет препроцессором, но он достаточно умен, что я думаю, что он должен быть включен:

class CSingleLock
{
    ...
};
#define CSingleLock class CSingleLock

Теперь забывая назвать временные результаты в ошибке, потому что в то время как следующее допустимо C++:

class CSingleLock lock(&m_criticalSection, true); // Compiles just fine!

тот же код, но опуская имя, не является:

class CSingleLock(&m_criticalSection, true); // <-- ERROR!

Я вижу, что за 5 лет никто не придумал самого простого решения:

#define LOCK(x) CSingleLock lock(&x, TRUE);
...
void f() {
   LOCK(m_criticalSection);

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