Disallowing Creation of the Temporary Objects

Disallowing creation of the temporary objects

Edit: As j_random_hacker notes, it is possible to force the user to declare a named object in order to take out a lock.

However, even if creation of temporaries was somehow banned for your class, then the user could make a similar mistake:

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

// do other stuff, assuming lock is held

Ultimately, the user has to understand the impact of a line of code that they write. In this case, they have to know that they're creating an object and they have to know how long it lasts.

Another likely mistake:

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

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

Which would lead you to ask "Is there any way I can stop the user of my class from allocating it on the heap"? To which the answer would be the same.

In C++0x there will be another way to do all this, by using lambdas. Define a function:

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

That function captures the correct usage of CSingleLock. Now let users do this:

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

This is much harder for the user to screw up. The syntax looks weird at first, but [&] followed by a code block means "Define a function that takes no args, and if I refer to anything by name and it is the name of something outside (e.g. a local variable in the containing function) let me access it by non-const reference, so I can modify it.)

Prevent the creation of temporary objects

Here are C++11 versions of two great answers to the same question. I don't deserve much credit for this, so consider voting for the original answers instead.

user1773602's answer

The idea is defining a deleted function with de same name as the class:

class ScopedLock {
// ...
};

void ScopedLock(...) = delete;

The usage is unusual and this might be (very?) inconvenient:

  class ScopedLock a; // OK             :-| but requires keyword 'class'
ScopedLock b; // Compiler error :-(
ScopedLock(); // Compiler error :-)

Johannes Schaub - litb's answer

If you don't mind having a runtime error (like the OP seems to do) rather than a compile time error then you can try this.

The basic idea is that a temporary bound to an argument of ScopedLock's constructor will be destroyed before or after the ScopedLock object depending on whether the later is a temporary or not.

class ScopedLock {

bool flag_ = true;

struct Flag {
bool* flag;
~Flag() {
*flag = false;
}
};

public:

ScopedLock(Flag&& f = Flag()) {
f.flag = &flag_;
}

~ScopedLock() {
if (flag_) {
std::cerr << "ScopedLock misuse\n.";
std::terminate();
}
}

};

With this, we have

ScopedLock a; // OK
ScopedLock(); // Runtime error

Can I forbid temporary objects as parameters?

If your parameter is not const, the function won't accept temporaries.

If your parameter is const, the function accepts both temporary and regular objects.

But if you want to prevent that, you can use the following

struct Object{};
void foo(const Object& o) {
/*only query o, don't alter it*/
}
void foo(Object&& ) = delete;

int main() {
Object o;
foo(o); // allow this
foo(Object{}); // but disallow this
}

Live

How to disallow temporaries

Another macro-based solution:

#define Foo class Foo

The statement Foo("hi"); expands to class Foo("hi");, which is ill-formed; but Foo a("hi") expands to class Foo a("hi"), which is correct.

This has the advantage that it is both source- and binary-compatible with existing (correct) code. (This claim is not entirely correct - please see Johannes Schaub's Comment and ensuing discussion below: "How can you know that it is source compatible with existing code? His friend includes his header and has void f() { int Foo = 0; } which previously compiled fine and now miscompiles! Also, every line that defines a member function of class Foo fails: void class Foo::bar() {}")

c++: what's the design philosophy of allowing temporary object to call non-const member function?

OK, I am putting some additional materials here. The following materials are quoted from David Vandevoorde, Nicolai M. Josuttis, Douglas Gregor-C++ Templates_ The Complete Guide-Addison-Wesley (2017), C.2.1 The Implied Argument For Member Functions.

“An old special-case permits an rvalue to be bound to an lvalue
reference to non-const type when that reference is the traditional
implicit *this parameter”

struct S{
void f1() {}//the old rule
void f2() && {}
void f3() & {}
};
int main()
{
S().f1();//Here, I THINK const this is bound to non-const, thus allowing calling non-const member functions.(the comment here is not quoted from the book.)
S().f2();
S().f3();//not okay
return 1;
}

Use -std=c++11 compilation option.
So, this means C++ designers have realized that the old rule of allowing temporary objects, which have implicit const this*, to call non-const member function is not so good. So in C++11 they introduced & and && suffixing function declaration.

But when it comes to the design philosophy of the old rule of allowing temporary objects calling non-const member function, I think it's not worth diving into this. C++ 11 has make an effort to "emend" this.(Or let programmer control this.)



Related Topics



Leave a reply



Submit