次の方法で共有


Reference values in C++

Reference values are a powerful feature of C++ but I find they have one significant detractor.  A developer can not look at an API call and determine if a parameter is being passed by reference or value (VB has the same problem). 

IMHO this is one item that C# got 100% correct.  In C# developers must say a value is out/ref or a compile error results.  Forcing both the API declaration and usage to specify the reference semantics makes code much more understandable.  When you look at an API call there is absolutely no question about the byref/byval/out semantics of a parameter. 

Internally I've met people who are hesitant to use reference parameters in C++ because of the ambiguity.  Not making it declarative in both places meant unexpected behavior could occur in a number of scenarios.  I agree with this statement. 

But hey we're talking about C++ here.  Any C++ problem can be fixed with some macros and a template right?  I thought about this over the weekend and came up with a quick sample.  Note, I haven't extensively tested this sample yet so there may be bugs.  However it gets the base cases right.

The goal of this API is to allow API authors to force developers to be explicit about their ByRef semantics.  It will prevent developers from silently passing a value by ref and hence getting unexpected behavior.  Failure to do so will result in a compile time error.  Also there is a minor bit of indirection overhead for debug mode but in retail this will compile out to normal code. 

 #ifdef DEBUG

template <typename T>
class ByRefType
{
public:
    explicit ByRefType(T& arg) : m_ref(arg)
    {
    }

    operator T&() const 
    {
        return m_ref;
    }

    ByRefType& operator=(const T& value)
    {
        m_ref = value;
        return *this;
    }

private:
    ByRefType();

    mutable T& m_ref;
};

template <typename T>
ByRefType<T> MakeByRefType(T& expr)
{
    return ByRefType<T>(expr);
}

#define ByRef(expr) MakeByRefType(expr)
#define ByRefParam(type) ByRefType<type> 

#else

#define ByRef(expr) expr
#define ByRefParam(type) type&

#endif

All well and good.  Now we can attribute byref paramaters with ByRefParam() and force callers to tag it as a ByRef argument.

 void SimpleByRef(ByRefParam(int) i, int newValue)
{
    i = newValue;
}

void Test()
{
    int i1;
    SimpleByRef(ByRef(i1), 5);
    SimpleByRef(i1, 6);     // Compiler Error!!!
}

As said before, this is an initial implementation and I expect updates as I use this in code and find bugs.  Please post back with any you find.

Comments

  • Anonymous
    April 03, 2008
    The comment has been removed
  • Anonymous
    April 03, 2008
    Mike,
  1. Yes, Macros are evil (yet so addictive).  In this case though the goal was to enforce declarative support of reference parameters with no shipping overhead. I agree the overhead is minimal and hopefully removed in RET but I can't guarantee it.  I've worked with many devs who are nervous/hestitant about adding compile time error checking that is built into RET code.  Therefore when I design such a pattern I only enforce it in DEBUG builds.   IMHO DEBUG only is good enough.  True you don't ship DEBUG builds but code shipped at some point was built with DEBUG.  Also RET is hard enough to debug without having to step through extra macros/templates.
  2. Yes this would need to be done in order to avoid the semaintcs you pointed out.