Hello,

I was just noticing that the 'const'ness of references often causes me to rewrite a large chunk of my code with pointers. For example if I have a class that needs access to a large amount of data from another class I would love to use a reference. However the only way to do that is to assign the references in the constructor (at least as far as I know) and therefore I can't re-assign the class at all (without complicated pointer conversions). As such I end up going through all my private data declarations and changing my references to pointers. This of course forces me to search my code for all the times I used the reference so that I can cast it. Not to mention that the class with all the data is for use by others, not by me (group project) and some of the others aren't yet comfortable enough with pointers to deal with them properly (Java programmers that just learned C++ in class). I was wondering if there is a way to say do this:

class someDataClass
{// private:
    const largeDataType &data;
    public:
    someDataClass &setDataRef(const largeDataType &o){data@=o;}//new operator for reference copy?
};

Instead of having to do this:

class someDataClass
{// private:
    const largeDataType *data;
    public:
    someDataClass &setDataRef(const largeDataType *o){data=o;}
};

I would have thought that if it wasn't possible that C++11 would have fixed that, given that it created the && move constructor, why couldn't it create some kind of reference re-assign operator?

PS: I see that a lot of people are already familiar with C++11 to the point that they don't think about it when they use its features. Is it now considered 'best practice' to use the new features? I am mainly concerned due to the lack of universal support for it and the fact that some C++ programmers have yet to learn the changes so I fear that C++11 code may not be understood.

Recommended Answers

All 12 Replies

Generally you shouldn't use assignment to initialize your members inside the constructor. As you noticed, it doesn't work. It also calls the default constructor, followed by the assignment operator, which might be needlessly expensive for some classes. Also it won't work if the default constructor or the assignment operator aren't defined for the given class.

Instead you should use initialization lists:

someDataClass &setDataRef(const largeDataType &o) : data(o) {}

Using initialization lists you can initialize references and const members without problems and you can initialize non-references using an constructor you like instead of the default constructor.

PS: I see that a lot of people are already familiar with C++11 to the point that they don't think about it when they use its features. Is it now considered 'best practice' to use the new features? I am mainly concerned due to the lack of universal support for it and the fact that some C++ programmers have yet to learn the changes so I fear that C++11 code may not be understood.

I would consider it 'best practice' in most circumstances. C++11 is practically two years old now and a lot of its feature set was known before it even officially became a standard. If people can't compile C++11 code now, it's because they're using some really old crap like Turbo C++ or dev-c++ or whatever. I can't think of a modern compiler that doesn't support a majority of its feature set.

@Tumlee - Thank you very much. Exactly the answer I expected. Is there some macro that can tell you if C++11 is supported (so that you can make a header work in C++03, but write it in C++11 and just have C++03 issue a warning?

@sepp2k - Wouldn't your solution have to either copy or move data from o into 'data'? What I want is for 'data' to reference the same object as 'o' references. (also I didn't know that you could use initializer lists for functions other than constructors, is it actually standard behaviour?)

Ah, I'm stupid. I though setDataRef was a constructor (I didn't pay attention to the name... or the fact that it had a return type). So, yeah, ignore my answer, you can't use initialization lists with methods. Sorry.

I see that a lot of people are already familiar with C++11 to the point that they don't think about it when they use its features. Is it now considered 'best practice' to use the new features?

Best practice depends on your needs. If you don't need to conform to legacy code or tools, then I'd say go for it. Otherwise, you really have no choice but to follow the supported standard. Note though, that C++11 isn't likely to go the way of C99. There's a huge demand for it (and also C++14 already) as well as a huge push to get conforming implementations shipped. It won't be long before most of the new example code out there is dependent on at least C++11.

Those Turbo C++ folks will just have to suffer and learn how to convert C++11 to pre-standard C++.

I am mainly concerned due to the lack of universal support for it and the fact that some C++ programmers have yet to learn the changes so I fear that C++11 code may not be understood.

Universal support is close enough at this point, with the majority of the major features supported by all popular compilers and the rest on the horizon. As far as not understanding code because one hasn't learned the changes...now is a good time to start. ;)

Is there some macro that can tell you if C++11 is supported (so that you can make a header work in C++03, but write it in C++11 and just have C++03 issue a warning?

If by "warning" you mean "error", sure:

#if __cplusplus <= 199711L
#error C++11 compiler support is required for this header.
#endif

In a perfect world you could support both standards and do this:

#if __cplusplus > 199711L
#include <my_header11.h>
#elif
#include <my_header03.h>
#endif

What I want is for 'data' to reference the same object as 'o' references.

Let's talk about ownership for a moment. Who owns the actual object that's being referenced? If someDataClass is going to own it, you should consider move semantics to transfer ownership to the someDataClass object. If some external force is going to own it, you've got a brittle design because someDataClass has no way of ensuring that the referenced object still exists.

What exactly are you trying to accomplish with this data sharing approach? I suspect you could modify your design to improve matters and then the problem of re-seating a reference (which should't be needed in the first place) becomes moot.

The purpose of this is to create a sort of AI game, in which you have to write an AI. As such I use a large set of classes to represent the objects of the game, then I have a controller class like so:

class Controller
{// private: (so that the opponent can't somehow use them)
    virtual string getPassword()=0;//so the player can set a password on the objects they control
    vector<Object*> &objects;//to give them quick-easy access to all the game objects
    //other vectors here, more specialized, for the less experienced programmers
    //for example I have vector<Unit*> &myUnits; to get access to just the objects this player can control
    virtual vector<Order> getOrders()=0;//to ask the player for orders.
    friend class Processor;//every Object is a friend of processor, I include this line in all of them
};
//a typedef for the display functon to be supplied to the processor (lets you display the map however you wish)
typedef void(*DisplayAdapterFunction)(vector<Tile*>&,//the game world
                                      vector<Order>&);//the order list
class Processor
{// private:
    //some helpful functions
    public:
    bool runGame(Controller *p1, Controller *p2, string map, int mapWidth, DisplayAdapterFunction daf)
    {
        //a ton of logic to actually run the game.
        //I use references in the Controller vectors so that I can 'point' them at the control vectors I use
        // that way I don't have to copy the entire vector each turn.
    }
}

I am assuming that by making my vectors in the Controller class constant they can't change the values. However I would very much like to be able to use references for them because as I said, many of the friends of mine for which I am making this game are Java programmers and not overly comfortable with pointers as of yet. Is it at all possible? Or should I just give them pointers? Is there a better solution (the code looks really sloppy from my end so as to keep it looking crisp from the user's end).

Is there any reason why the Controller can't own the objects and then dole out references to them on request? Since you're storing pointers, and from the previous discussion, I'm assuming the objects aren't owned by the Controller, just loosely contained. Is that the case?

given that it created the && move constructor, why couldn't it create some kind of reference re-assign operator?

C++11 did that with std::reference_wrapper<>. A small class provided by the library, which fills the syntactic gap. Without having to redefine C++98 references; without causing existing code to break.
http://en.cppreference.com/w/cpp/utility/functional/reference_wrapper

#include <vector>
#include <list>
#include <functional>
#include <algorithm>
#include <iostream>

int main()
{
    const std::list<int> lst { 5, 4, 9, 3, 1, 8, 0 } ;
    for( const int& i : lst ) std::cout << i << " (" << &i << ") "  ;
    std::cout << '\n' ;

    std::vector< std::reference_wrapper< const int > > vec( lst.begin(), lst.end() ) ;
    std::sort( vec.begin(), vec.end() ) ;
    for( const int& i : vec ) std::cout << i << " (" << &i << ") "  ;
    std::cout << '\n' ;
}

http://ideone.com/ldNu1U

That looks like exactly what I want... I just have to ask one question: To a person using that reference, in what ways does it act differently to a normal reference (IE: if I gave you the object and told you that it was a normal reference when in reality it was a reference_wrapper, how would it be any different?)

Thank you. Unfortunately I cannot use reference_wrappers here because of the fact that it overrides the = operator. While this allows it to do what it has to do, it does mean that it cannot be documented as a normal reference. Instead I have decided to restructure the entire program using wrapper classes. In that way I also don't have to abuse the friend relationship, and it will be harder to hack (always an issue when writing games for programmers).

I was wondering if there is a way to say do this:
...
Instead of having to do this:
...
I would have thought that if it wasn't possible that C++11 would have fixed that, given that it created the && move constructor, why couldn't it create some kind of reference re-assign operator?

Well, you came very close to the answer, sort of by accident, by mentioning the rvalue-references (&&). With your two examples, you missed out on one possibility, which is what is done in a standard class like std::reference_wrapper, i.e., the following simple trick:

class someDataClass
{// private:
    const largeDataType *data;
  public:
    someDataClass& setDataRef(const largeDataType& o) {  // take by reference
      data = &o;   // store the address
      return this;
    };
};

The only problem with the above solution is that because in C++ there is a rule to allow temporary values (rvalue) to be bound to a const-reference, which would allow people to call this function with a temporary value, of which taking the address would not be a good idea, obviously. But, with C++11, you can easily remove that possibility and that danger:

class someDataClass
{// private:
    const largeDataType *data;
  public:
    someDataClass& setDataRef(const largeDataType& o) {  // take by reference
      data = &o;   // store the address
      return this;
    };

    someDataClass& setDataRef(largeDataType&&) = delete;  // forbid calling with rvalue.
};

In the above, the rvalue-reference overload will be selected whenever an rvalue is passed, and thus, trigger a compilation error as an attempt to call a deleted function.

So, that solves the technical problem of being able to store a pointer within the class and allow the class to be used as if it stored a reference (i.e., like a reference-wrapper). But, as others have pointed out, except wielding out the immediate danger caused by storing the address (or reference) to what could be an rvalue (temporary), there is the obvious problem of ambiguous ownership.

This can be a hard design issue. And if what you are trying to do is make it easy for Java programmers, then your best choice might be to use std::shared_ptr everywhere. It is generally not recommended to use std::shared_ptr everywhere for the simple reason that it is rarely the appropriate solution (sufficient and light-weight), however, Java programmers are not known for their ability to select the best tool for the job, but rather for selecting the most care-free solution, and in C++, short of having garbage collection, std::shared_ptr is exactly that.

In reality, this is really about the design of your ownership relationships. As it is often said, ownership is an a-cyclic directed multi-graph. Meaning that many times, it is just a tree, with some distracting mess all over it. In other words, if you take a piece of paper and a pen (yes, those things still exist!), and you draw a circle for each kind of object involved in your design, and then you draw arrows for each object that "depends on" the existence of another (e.g., if A depends on B, draw an arrow from A to B). What you get at the end should be an a-cyclic graph. Each arrow represents a std::shared_ptr stored in the parent and that points to the child. If there are cycles, then there must be one arrow that isn't completely needed (maybe just "weakly" needed, as in, using std::weak_ptr instead). That is sort the technique for "ownership design for dummies", which is what Java programmers are. ;) At least, when it comes to understanding this particular topic.

If you read my tutorial, you'll see a broader perspective on this issue, which mostly have to do with the different kinds of "arrows" you can draw on your little ownership diagram, and how very often by-value ownership works best given that you can design your interfaces with the idea of "borrowing" in mind (and things like "fly-weight" patterns). But, of course, this will be even more disturbing to your Java friends (i.e., value-semantics, simple and deterministic ownership trees, and the use of procedural interfaces instead of "pure" OOP style, tend to be very upsetting to Java programmers).

I see that a lot of people are already familiar with C++11 to the point that they don't think about it when they use its features. Is it now considered 'best practice' to use the new features? I am mainly concerned due to the lack of universal support for it and the fact that some C++ programmers have yet to learn the changes so I fear that C++11 code may not be understood.

Yeah, that's a sensitive issue. I think that at this point, it is pretty safe to use C++11, but some restraint might be advisable. I personally tend to make very conservative use of C++11 features. I mostly use things that are fairly widely supported by even the earlier adopters and have clear benefits beyond just making it easier to code (like move-semantics (rvalue-references) and variadic templates). So, this is mostly what I use, and I tend to exclude things like lambdas, concurrency, inheriting / delegating constructors, etc.. because support is a bit more shaky with those features.

But at the end of the day, it is really just a matter of what level of legacy compilers you want to support. And requiring decent C++11 support these days is not an unreasonable request since good C++11 compliant (or mostly compliant) compilers are available from all major vendors and for all platforms.

As for flags, you can use the standard __cplusplus flag (which is also the standard version number) to verify compliance of the compiler. If you use Boost, you can also use the <boost/config.hpp> header to generate a large set of flags that will give you all the information you can desire about what individual features (of C++98 / 03 / 11) are supported (or not) by the compiler. So, you can use that if you want to implement backward compatible versions of any code that would be otherwise use C++11 features (as I currently do).

As for being understood, I would second deceptikon on that, it's about time to get the ball rolling. As always, the principle of "clear code" over "fancy code" still applies. There is no point in abusing some of the new C++11 syntax features just to make things really "fancy" if there is a chance it will just confuse people.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.