Alex Blekhman
Alex Blekhman

Reputation: 3985

Const correctness with temporary instances

Here is the example of the "scoped lock" idiom with common mistake: no local variable is created, so lock is not in effect. This code compiles flawlessly both with VC++ 2010 and Comeau C++ online:

class Mutex
{
public:
    void lock() {}
};

class ScopedLock
{
public:
    ScopedLock() : m_pm(0) {}
    ScopedLock(Mutex& m) : m_pm(&m) { m_pm->lock(); }

private:
    Mutex* m_pm;

private:
    ScopedLock& operator =(const ScopedLock&);
    ScopedLock(const ScopedLock&);
};

class X
{
public:
    void foo() const
    {
        ScopedLock(m_mutex);
    }

private:
    Mutex m_mutex;
};


int main()
{
    X x1;
    x1.foo();
}

If default constructor for ScopedLock is commented out, then both compilers give an error:

error C2512: 'ScopedLock' : no appropriate default constructor available

(When ScopedLock used correctly, i.e. local variable is created: ScopedLock guard(m_mutex);, then compilation fails as expected. Declaring m_mutex as mutable fixes the problem.)

I have two questions:

  1. Why X::foo compiles? It seems that compiler was able to cast const Mutex& to Mutex& somehow.

  2. What role plays ScopedLock default constructor, so the compilation succeeds?

Thanks.

Update: I found the answer. It appears that ScopedLock(m_mutex); statement creates a local variable m_mutex of type ScopedLock. Not a temporary. That's why ScopedLock::ScopedLock default constructor is required.

Upvotes: 6

Views: 318

Answers (3)

decltype
decltype

Reputation: 1601

You answered the question yourself.

It appears that ScopedLock(m_mutex); statement creates a local variable m_mutex of type ScopedLock

The explanation is to be found in the Standard's Section 6.8 Ambiguity Resolution:

There is an ambiguity in the grammar involving expression-statements and declarations: An expression-statement with a function-style explicit type conversion [5.2.3] as its leftmost subexpression can be indistinguishable from a declaration where the first declarator starts with a (. In those cases the statement is a declaration.

The Standard then lists T(a); as an example of a statement that is really a declaration. It is equivalent to T a;

This is one variation of the infamous C++ "most vexing parse".

Upvotes: 4

Bukes
Bukes

Reputation: 3708

The problem is that the X::foo() method is declared as const - that means that it will not mutate (change) the object.

The ScopedLock() constructor doesn't have a overload that accepts a immutable (const) reference to a Mutex object.

Fixing this requires you declare m_mutex as mutable, or provide an appropriate overloaded ScopedLock() constructor. I'm thinking the former is preferable.

Upvotes: 0

Aaron McDaid
Aaron McDaid

Reputation: 27123

I'm pretty sure your problem is line 26: ScopedLock(m_mutex);

Instead, that should be something like ScopedLock a_variable_name(m_mutex);

When I make that change, I get the expected errors:

constCorrectness.cpp: In member function ‘void X::foo() const’:
constCorrectness.cpp:26: error: no matching function for call to ‘ScopedLock::ScopedLock(const Mutex&)’
constCorrectness.cpp:18: note: candidates are: ScopedLock::ScopedLock(const ScopedLock&)
constCorrectness.cpp:11: note:                 ScopedLock::ScopedLock(Mutex&)
constCorrectness.cpp:10: note:                 ScopedLock::ScopedLock()

Perhaps somebody can interpret ScopedLock(m_mutex) for us? Does it declare a function or something? Instead of calling a constructor as expected by the questioner? Update: Striking this out. I think it's just a variable declaration (i.e. the brackets are ignored.)

Upvotes: 1

Related Questions