I've been programming C++ for a while now and I'm starting to doubt that the rule use references whenever possible should be applied everywhere.
Unlike this related SO post I'm interested in a different kind of thing.
In my experience the reference/pointer mix messes up your code:
std::vector<Foo *> &x = get_from_somewhere(); // OK? reference as return value
some_func_pass_by_ref(x); // OK reference argument and reference variable
some_func_by_pointer(x[4]); // OK pointer arg, pointer value
some_func_pass_elem_by_ref(*x[5]); // BAD: pointer value, reference argument
some_func_that_requires_vec_ptr(&x); // BAD: reference value, pointer argument
One option would be to replace &
with * const
like Foo &
with Foo * const
void some_func_by_ref(const std::vector<Foo * const> * const); // BAD: verbose!
this way at least the traversals are gone. and me rewriting function headers is gone, because all arguments will be pointers... at the price of polluting the code with const
instead of pointer arithmetic (mainly &
and *
).
I would like to know how and when you apply use references whenever possible rule.
considering:
increasing readability
*
to transform Foo*
to Foo&
and vice versa* const
NOTES: one thing I figured is to use pointers whenever I intend to ever put the element into an STL container (see boost::ref)
I don't think this is something C++03 specific but C++11 solutions are fine if they can be backported to C++03 (i.e.: NRVO instead of move-semantics).
When should I use references in C++?
When you need to treat a variable like the object itself (most cases when you don't explicitly need pointers and don't want to take ownership of an object).
I would like to know how and when you apply use references whenever possible rule.
whenever possible, except when you need to:
Bjarne Stroustrup stated in his book that he introduced references to the language because operators needed to be called without making a copy of the object (that would mean "by pointer") and he needed to respect syntax similar to calling by value (that would mean "not by pointer") (and thus references were born).
In short, you should use pointers as little as possible:
const &
std::shared_ptr
, std::unique_ptr
, your_raii_pointer_class_here
- not by (raw) pointer.get()
for getting the raw pointer.one thing I figured is to use pointers whenever I intend to ever put the element into an STL container (or can I get rid of this?)
You can use Boost Pointer Container library.
IMHO the rule stands because raw pointers are dangerous because ownership and destruction responsibility becomes rapidly unclear. Hence the multiple encapsulations around the concept (smart_ptr
, auto_ptr
, unique_ptr
, ...).
First, consider using such encapsulations instead of raw pointer in your container.
Second, why do you need to put pointers in a container ? I mean, they're meant to contain full objects ; they have an allocator as template argument for precise memory allocation after all. Most of the time, you want pointers because you have an OO-approach making heavy use of polymorphism. You should reconsider this approach. For example you can replace:
struct Animal {virtual std::string operator()() = 0;};
struct Dog : Animal {std::string operator()() {return "woof";}};
struct Cat : Animal {std::string operator()() {return "miaow";}};
// can not have a vector<Animal>
By something like this, using Boost.Variant :
struct Dog {std::string operator()() {return "woof";}};
struct Cat {std::string operator()() {return "miaow";}};
typedef boost::variant<Dog, Cat> Animal;
// can have a vector<Animal>
This way when you add a new animal, you inherit nothing, you just add it to the variant.
You can also consider, a little bit more complicated, but far more generic, using Boost.Fusion :
struct Dog {std::string talk; Dog() : talk("wook"){}};
struct Cat {std::string talk; Cat() : talk("miaow"){}};
BOOST_FUSION_ADAPT_STRUCT(Dog, (std::string, talk))
BOOST_FUSION_ADAPT_STRUCT(Cat, (std::string, talk))
typedef boost::fusion::vector<std::string> Animal;
int main()
{
vector<Animal> animals;
animals.push_back(Dog());
animals.push_back(Cat());
using boost::fusion::at;
using boost::mpl::int_;
for(auto a : animals)
{
cout << at<int_<0>>(a) << endl;
}
}
This way you do not even modify an aggregate like variant nor the algorithms on animals, you just need to provide a FUSION_ADAPT matching the used algorithms prerequisites. Both versions (variant and fusion) let you define orthogonal object groups, a useful thing you can not do with inheritance trees.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With