Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When should I use references in C++?

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:

  • minimal rewriting of function prototypes (i.e.: oh damn I need need to rewrite alot of prototypes because I want to put this referenced element into a container)
  • increasing readability

    • avoid application of * to transform Foo* to Foo& and vice versa
    • avoid excessive const usage as in * 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).

like image 275
Alexander Oh Avatar asked Jan 14 '23 00:01

Alexander Oh


2 Answers

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:

  • work on the address (log the address, diagnose or write custom memory allocation, etc)
  • take ownership of parameter (pass by value)
  • respect an interface that requires a pointer (C interoperability code and legacy code).

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:

  • if the value is optional ("can be null") then use a std::optional around it, not a pointer
  • if you need to take ownership of the value, receive parameter by value (not a pointer)
  • if you need to read a value without modifying it, receive parameter by const &
  • if you need to allocate dynamically or return newly/dynamically allocated object, transmit value by one of: std::shared_ptr, std::unique_ptr, your_raii_pointer_class_here - not by (raw) pointer
  • if you need to pass a pointer to C code, you should still use the std::xxx_ptr classes, and get the pointer using .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.

like image 165
utnapistim Avatar answered Jan 22 '23 09:01

utnapistim


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.

like image 27
lip Avatar answered Jan 22 '23 07:01

lip