Is it legal C++ to test the this-pointer in a memb

2019-02-23 17:29发布

问题:

I have an application involving objects of different class types. The objects are referenced by pointers. A null pointer signifies that the associated object does not exist. Currently the calling codes is cumbersome, because each time it uses a pointer to an object, it tests the pointer value for null, and take some appropriate action it is null. Because the default action to be taken in the case of non-existence depends on the type of object, I would prefer to encode it in the classes for the objects themselves rather than in the calling program. This results in constructions like the following:

class C
{ ... 
  void member_func() //non-virtual !
  { if (this) { do something with the object ... }
    else { take some default action }
  }
  ...
};

Clearly the member function cannot be virtual, because the lookup table does not exist when the object does not exist, and the virtual call would fail. But is this code legal C++ for non-virtual member functions? It seems to work correctly for the compilers I have tried it on, but I am worried about possible non-portability. In the standard I can’t find a clause that either expressly allows or expressly prohibits such constructions.

回答1:

this will never be null in a member function so the check you perform is useless.

As pointed by Matthieu M. in a comment, if you do something like this in your code:

C* c = 0; 
c->member();

This would cause undefined behavior and that is something bad.



回答2:

As has been pointed out, this can never be a null pointer. If it is, you've already invoked undefined behavior. You could, instead, create a set of overloaded functions, like this:

void DoTheThing(C* cp)
{
    if (cp)
        cp->member_func();
    else
    {
        // take some default action
    }
}

void DoTheThing(B* bp)
{
    if (bp)
        bp->some_other_member_func();
    else
    {
        // take some default action
    }
}

If the function you want to call has the same name in each class, then you could make a static function in each class which performs the default action for that class (all with the same name), and make a template:

template<typname T>
void DoTheThing(T* tp)
{
    if (tp)
        tp->member_func();
    else
        T::default_action()
}


回答3:

Standard-wise, the code is not legal, but it's used in practice (bad practice that is).

In fact, IIRC MFC uses these checks internally.



回答4:

Checking whether this == NULL is not a problem. Calling a method through a NULL object pointer is.

If you want to keep the checks somewhere, you could put it in a smart pointer class which can take the appropriate action if the held pointer is NULL. If the "appropriate action" is uniquely determined by the held type, you can use a traits class to specify it.

This way your NULL checks and their logic is kept together, and not mixed into either the caller or the method code.


// specialize this to provide behaviour per held type
template <typename T> struct MaybeNullDefaultAction {
    void null_call() { throw std::runtime_error("call through NULL pointer"); }
}

template <typename T> class MaybeNull: MaybeNullDefaultAction<T> {
    T *ptr;
public:
    explicit MaybeNull(T *p) : ptr(p) {}

    T* operator-> () {
        if (!ptr)
            null_call();
        // null_call should throw to avoid returning NULL here
        return ptr;
    }
};

Unfortunately, I can't see a way to do this without throwing. There's no way to intercept function calls for all methods names, otherwise I'd just return *this from operator-> and do the work in operator().



回答5:

I think, it's not allowed to do that. You asked for references to the standard. I believe first thing of interest is 9.3.1 Nonstatic member functions, 1.:

A non-static member function may be called for an object of its class type, or for an object of a class derived (Clause 10) from its class type, using the class member access syntax (5.2.5, 13.3.1.1).

Second let's look at 5.2.5 Class member access, 2.:

The expression E1->E2 is converted to the equivalent form (*(E1)).E2; the remainder of 5.2.5 will address only the first option (dot).

So if E1 is a nullptr, the *E1 will not be allowed. So at least is my guess.



标签: c++ null this