5

Inpired by this answer I'm using the following solution for read-only member variables:

template <class T, class OWNER>
class readonly
{
    friend OWNER;

public:
    explicit readonly(const T &t) : m_t(t)
    {
    }

    ~readonly()
    {
    }

    operator const T&() const
    {
        return m_t;
    }

private:
    T& operator =(const T &t)
    {
        m_t = t; 
        return m_t;
    }

    T m_t;
};

Which works great, to optimize performance a little I use it like this:

class A
{
public:
    A()
    {
    }

    ~A()
    {
    }

#ifdef _DEBUG     // DON'T USE THIS SWITCH, SEE ANSWERS BELOW!
    readonly<int, A> m_x, m_y;
#else
    int m_x, m_y;
#endif
};

However I would love to eliminate the precompiler switch which checks if we're doing a debug or release build... Does anyone see a solution using a macro or clever template trick?

EDIT: I've checked the performance in a loop, it generates about 15~20% overhead using VS2010. It does not result in the same code, automatic inlining enabled.

EDIT #2: I've created a unit test, eliminating all other stuff. I have no performance loss anymore, so great, there was no problem after all. Thanks for the help! And I've fixed the constructor, good call.

Community
  • 1
  • 1
demorge
  • 1,097
  • 1
  • 7
  • 17
  • A loop of what? And under what settings? Also you do realise that even if it has overhead, it'll probably be negligible anyway, right? – Cat Plus Plus May 24 '12 at 19:04
  • Nothing is neglectable when it is used in your main drawing routine ;) And it's just a simple loop: multiplying the members of A with each other add some others values and store the result. – demorge May 24 '12 at 19:08
  • 2
    Added C++11 tag. In C++03 you cannot befriend a template argument (`friend OWNER;` is not valid syntax) – David Rodríguez - dribeas May 24 '12 at 19:21
  • Why would you offer a protected `operator=`? Do you intend on deriving from this type? – David Rodríguez - dribeas May 24 '12 at 19:28
  • 1
    It could be private too, force of habit :) – demorge May 24 '12 at 19:30
  • If you really wanted it readonly, `operator const T&() const` should be changed to `operator T() const` to return a *copy* of the data, otherwise someone could simply `const_cast` away the `const` portion of the `T&`, making the referenced data accessible to writing. – Remy Lebeau Apr 01 '14 at 01:29

7 Answers7

6

Your optimisation is useless and will result in the exact same code. All of readonly is trivial and will be inlined, eliminating any overhead it might have over using raw T. So, the solution is to not fix a problem that doesn't exist, and just use readonly<int, A> regardless of whether this is debug build or not.

As @MooingDuck noted, you should change your constructor to use init list (and probably make it explicit, too).

Cat Plus Plus
  • 125,936
  • 27
  • 200
  • 224
  • well, it would if `readonly` was done well. The constructor is poorly written. – Mooing Duck May 24 '12 at 19:06
  • 3
    Worse than *will result in the same code*: Can generate *different* code in ways you might not really want (i.e. removal of the user defined conversion might enable different overloads to be picked up and that might end with different semantics in your application). – David Rodríguez - dribeas May 24 '12 at 19:22
3

Use a helper metafunction:

template< typename T, typename Owner >
struct make_read_only
{
#ifdef _DEBUG
    typedef readonly< T, Owner > type;
#else
    typedef T type;
#endif
};

and change your member declaration to:

make_read_only< int, A >::type;
K-ballo
  • 80,396
  • 20
  • 159
  • 169
2

Your approach suffers of what to me is a great problem: it can generate different code in DEBUG and RELEASE mode. And here I am not thinking performance, but rather semantics. I would be surprised if the optimizer is not able to generate equivalent binaries.

In the DEBUG version, a user defined conversion is required whenever the element is used, but in RELEASE that conversion is dropped, that can in turn enable the use of a different user defined conversion and cause a different overload to be picked when the member attribute is passed as an argument to a function.

While might not be a common case, if you do hit it you will have a good amount of debugging pain trying to determine why the application consistently fails in RELEASE but you cannot debug it with your DEBUG version...

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Thanks for the heads up, I guess you're right: you never know in which obscure scenario you may use such a class in the future. I'm using the template in both DEBUG and RELEASE builds now. – demorge May 24 '12 at 19:29
1

There is no performance aspect to const modifiers. Bot your debug and release versions will result in the same code, given a good optimizer.

Daniel
  • 30,896
  • 18
  • 85
  • 139
0

Here's a slightly different take on it. If you want a readonly variable but don't want the client to have to change the way they access it, try this templated class:

template<typename MemberOfWhichClass, typename primative>                                       
class ReadOnly {
    friend MemberOfWhichClass;
public:
    inline operator primative() const                 { return x; }

    template<typename number> inline bool   operator==(const number& y) const { return x == y; } 
    template<typename number> inline number operator+ (const number& y) const { return x + y; } 
    template<typename number> inline number operator- (const number& y) const { return x - y; } 
    template<typename number> inline number operator* (const number& y) const { return x * y; }  
    template<typename number> inline number operator/ (const number& y) const { return x / y; } 
    template<typename number> inline number operator<<(const number& y) const { return x <<y; }
    template<typename number> inline number operator>>(const number& y) const { return x >> y; }
    template<typename number> inline number operator^ (const number& y) const { return x ^ y; }
    template<typename number> inline number operator| (const number& y) const { return x | y; }
    template<typename number> inline number operator& (const number& y) const { return x & y; }
    template<typename number> inline number operator&&(const number& y) const { return x &&y; }
    template<typename number> inline number operator||(const number& y) const { return x ||y; }
    template<typename number> inline number operator~() const                 { return ~x; }

protected:
    template<typename number> inline number operator= (const number& y) { return x = y; }       
    template<typename number> inline number operator+=(const number& y) { return x += y; }      
    template<typename number> inline number operator-=(const number& y) { return x -= y; }      
    template<typename number> inline number operator*=(const number& y) { return x *= y; }      
    template<typename number> inline number operator/=(const number& y) { return x /= y; }      
    template<typename number> inline number operator&=(const number& y) { return x &= y; }
    template<typename number> inline number operator|=(const number& y) { return x |= y; }
    primative x;                                                                                
};      

Example Use:

class Foo {
public:
    ReadOnly<Foo, int> x;
};

Now you can access Foo.x, but you can't change Foo.x! Remember you'll need to add bitwise and unary operators as well! This is just an example to get you started

Jonathan
  • 6,741
  • 7
  • 52
  • 69
0

I like these two solutions far more.

The first uses private and inline:

class A
{
    public:
        inline int GetI();
        {
            return i;
        }

    private:
        int i;
}

the second uses const and const_cast:

class A
{
    public:
        const int I;

        void SetI(int newI)
        {
            //Verify newI or something.

            const_cast<int>(I) = newI;
        }
}
Andrew
  • 5,839
  • 1
  • 51
  • 72
-2

I am not 100% sure, but it looks like it is not possible to do it with templates. Because templates have no means to read preprocessor variables. To make your code more readable, you may define macro like READONLY_INT and declare it inside other .h file.

Stepan Yakovenko
  • 8,670
  • 28
  • 113
  • 206