Search code examples
c++inheritanceusing-declaration

Downsides to the 'Using' keyword in C++ as applied to derived classes


I recently discovered a new application of the using keyword; not with reference to the namespace functionality but inside a derived class declaration. In my case this was pertinent in regards the issues surrounding the 'operator=' member-function.

I had a situation where, given the declarations:

class CString{
public:
    ...//Various functions.
    operator=(const CString &cString)
    {
        //code to share internal array and increment reference count
    }
    operator=(const wchar_t *pstrString)
    {
        //code to create a personal internal array,
        //resize it appropriately and copy the pstrString argument.
    }
    ...
};

class CStringEx : public CString{
    ...//various members that expand upon CString's abilities.
};

...an object of CStringEx did not work as I expected:

CStringEx cString;

cString=L"String contents";

Instead a compiler error was generated stating 'CStringEx has no 'operator=()' function that takes an argument of type wchar_t*' (or - very close - words to that effect). After quite a bit of study I learned this is because even the automatically compiler-generated operator= member-functions of a derived class override those inherited from its parent class. This seems counter-intuitive and user-UNfriendly to me.

However, if I add a using keyword:

class CStringEx : public CString{
public:
    using CString::operator=;
    ...
};

...the child class will now use its parent's operator= member function and all is well.

So far, so good. However, after further reading here and elsewhere I have learned that many programmers do not like to utilize using for this purpose. For instance I have read some commentators who describe potentially unwanted side-effects, such as brining in ALL the operator= from the parent. However, again, other than in very specific circumstances I do not understand why inheriting all the parent member-functions would be and issue. If this is the major concern could someone explain the general dangers of doing so?

The only alternative I can think of is to write out stub functions in the child class for every operator= member-function of it's parent and then explicitly call those respective member-functions:

class CStringEx : public CString{
public:
    ...
    const CStringEx& operator=(const wchar_t* pstrString)
    {
        CString::operator=(pstrString);
        return *this;
    }
    const CStringEx& operator=(const CString &cString)
    {
        CString::operator=(cString);
        return *this;
    }
    ...//and so on...
};

When compared to the version with using CString::operator= this looks extremely ugly, cumbersome and messy to me. So again, why not use the using keyword?


Solution

  • This is a bit subjective so let's review what we know:

    If using is the right tool for the job, then you should use it. That said:

    • using will always bring in all the parent methods/operators, even newly added ones that you never intended. This might even result in broken code when someone creates a parent assignment operator that doesn't interact well with the child object.
    • Bringing in the parent's copy assignment operator means you can create a CStringEx from a CString but perhaps this is intended behavior.
    • Future readers/maintainers may not be familiar with the operator using syntax and may make the code slightly harder to grok.

    Given the fact that you're questioning it originally (based on things you've heard) and the points above, let's step back and look at your design a moment. The CStringEx has two possibilities:

    1. It has additional data members. In this case I'm willing to assert that inheriting parent operators is going to do something wrong in some or all cases, since the child class members won't be handled in the assignment.
    2. It does not have additional data members, it only provides additional code functionality. In this case, please don't be tempted to use a child class. Instead write free-function algorithms that operate on either (iterator) ranges or, if you must, CString objects.