代码之家  ›  专栏  ›  技术社区  ›  Naveen

不允许创建临时对象

  •  26
  • Naveen  · 技术社区  · 15 年前

    CSingleLock(&m_criticalSection, TRUE);
    

    请注意,它正在创建CSingleLock类的未命名对象,因此critical section对象将在该语句之后立即解锁。这显然不是编码员想要的。这个错误是由一个简单的打字错误引起的。我的问题是,是否有某种方法可以防止在编译时创建类的临时对象,即上述类型的代码应生成编译器错误。一般来说,我认为每当一个类试图获取某种资源时,就不应该允许该类的临时对象。有没有办法强制执行?

    7 回复  |  直到 15 年前
        1
  •  14
  •   Daniel Earwicker    15 年前

    正如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();
    }
    

    该函数捕获csingelock的正确用法。现在让用户这样做:

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

    这对于用户来说更难搞砸。语法一开始看起来很奇怪,但是[&]后面跟一个代码块意味着“定义一个不带参数的函数,如果我通过名称引用任何东西,并且它是外部事物的名称(例如,包含函数中的局部变量),那么让我通过非常量引用访问它,这样我就可以修改它。)

        2
  •  6
  •   Community CDub    7 年前

    第一 Earwicker makes some good points --您无法防止此构造的每一次意外误用。

    但对于您的具体情况,这实际上是可以避免的。这是因为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() 无法绑定到临时文件。

    也许令人惊讶的是,类方法 对临时人员进行操作——这就是原因 锁() 需要是一个自由函数。如果你放弃了 friend 说明符和要生成的顶部代码段中的函数参数 方法,则编译器将允许您编写:

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

    MS编译器注意: Visual Studio.NET 2003之前的MSVC++版本错误地允许函数绑定到VC++2005之前版本中的非常量引用。 This behaviour has been fixed in VC++ 2005 and above .

        3
  •  3
  •   anon anon    15 年前

    不,这是不可能的。这样做会破坏几乎所有依赖于创建无名暂时性的C++代码。针对特定类的唯一解决方案是将其构造函数设置为私有,然后始终通过某种工厂来构造它们。但我认为治疗比疾病更糟糕!

        4
  •  2
  •   ChrisF    15 年前

    虽然这不是一件明智的事情——正如您在bug中发现的那样——但该声明并没有“非法”之处。编译器无法知道该方法的返回值是否“重要”。

        5
  •  2
  •   aJ.    15 年前

    特殊情况下,如收缩向量,确实需要创建临时对象。

    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
  •  1
  •   PfhorSlayer    10 年前

    下面呢?有点滥用预处理器,但它足够聪明,我认为应该包括:

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

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

    相同的代码(但省略了名称)不是:

    class CSingleLock(&m_criticalSection, true); // <-- ERROR!
    
        7
  •  0
  •   Ryan Haining    4 年前

    老问题,但我有几点要补充。

    通过定义一个与类同名的宏函数,当有人忘记变量名时,可以触发带有有用消息的静态断言。 live here

    class CSingleLock {
     public:
      CSingleLock (std::mutex*, bool) { }
    };
    
    // must come after class definition
    #define CSingleLock(...) static_assert(false, \
        "Temporary CSingleLock objects are forbidden, did you forget a variable name?")
    

    won't match when there is a variable name . 但是, doesn't help in the case of uniform initialization CSingleLock{&m, true} . PfhorSlayer's answer 与统一初始化一起使用,因此使用起来更安全,但会产生更混乱的错误消息。我还是会把这个解决方案强过我的。不幸的是,所有这些宏观解决方案 fail when the type is in a namespace .


    [[nodiscard]]

    class CSingleLock {
     public:
      [[nodiscard]] CSingleLock (std::mutex*, bool) { }
    };
    

    clang will warn you 说:

    warning: ignoring temporary created by a constructor declared with 'nodiscard' attribute [-Wunused-value]
      CSingleLock(&m, true);
      ^~~~~~~~~~~~~~~~~~~~~
    

    GCC 9.2似乎存在以下问题: [[nodiscard]] on constructors 虽然还是 gives additional warnings fixed on head 在撰写本文时,这是wandbox上的gcc-10 20191217。

        8
  •  -1
  •   Paavo    10 年前

    我看到5年来没有人提出最简单的解决方案:

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

    现在只使用这个宏来创建锁。再也没有机会创造临时工了!这还有一个额外的好处,即宏可以很容易地扩充,以便在调试构建中执行任何类型的检查,例如检测不适当的递归锁定、记录文件和锁行,等等。