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

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

CSingleLock(&m_criticalSection, TRUE);

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

Ответ 1

Изменить: Как отмечает 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...

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

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

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

Ответ 2

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

Но для вашего конкретного случая на самом деле этого можно избежать. Это потому, что С++ делает одно (странное) различие в отношении временных объектов: Свободные функции не могут принимать неконстантные ссылки на временные объекты. Таким образом, чтобы избежать блокировок, которые появляются или исчезают, просто переместите код блокировки из конструктора 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 COMPILER ПРИМЕЧАНИЕ.. Версия MSVС++ до Visual Studio.NET 2003 неправильно разрешала функциям связываться с неконстантными ссылками в версиях до VС++ 2005. Это поведение было исправлено в VС++ 2005 и выше.

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

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

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

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

Ответ 7

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

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

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

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

Тот же код, но без имени, не указан:

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