Вопрос

При отладке сбоя в многопоточном приложении я наконец обнаружил проблему в следующем операторе:

CSingleLock(&m_criticalSection, TRUE);

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

Это было полезно?

Решение

Редактировать: Как отмечает 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 будет другой способ сделать все это — использовать лямбды.Определите функцию:

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.
    });

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

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

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

Но в вашем конкретном случае этого действительно можно избежать.Это потому, что в 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.

Поскольку неконстантный параметр ref Lock() не может привязаться к временному.

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

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

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

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

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

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

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

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

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);

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top