简体   繁体   中英

C++ Reference vs Pointer best practices

I have read a number of stackoverflow answers about this and I am not quite satisfied from the responses so I wanted to gather them here.

When calling a function using non-primitive and complex objects for parameters it is generally advised to pass them by reference UNLESS there is a possibility of the parameter being NULL. This is mostly due to the fact that references are guaranteed to have a value so you don't have to check if a parameter reference is NULL.

When the return value of a function is a non-primitive and complex object you should generally return a pointer because it is easy to confuse a function that returns a reference with one that returns a value as they appear identical when called.

As an example take this class

struct Rectangle
{
    int x, y;
    int width, height;

    Rectangle(int x_, int y_, int width_, int height_)
        : x(x_)
        , y(y_)
        , width(width_)
        , height(height_)
    {
    }
};

class Image
{
public: 
    Image()
        : dimensions(0, 0, 0, 0)
    {}
    ~Image();

    void SetDimensions (const Rectangle& newDimensions) // Pass by reference is recommended practice
    {
        dimensions = newDimensions;
    }

    Rectangle GetDimensions()       // Returning by value is usually the best practice in this case
    {
        return dimensions;
    }
private:
    Rectangle dimensions;
};

Are these the best practices for references vs pointers in function parameters and return types? If not, please explain why not.

EDIT 1: Changed GetDimensions() to return value because it is believed efficient

A reference is a reference and a value is a value. They've different semantics and only thinking to the performance of saving a copy operation can become a serious problem.

More specifically in theory references should be used only when the receiver cares about the identity (and not just about the value) of the object. Values should be used instead if the identity is not important and the called function/method cares only about the value.

If you're absolutely sure that you're not going to run into aliasing or lifetime issues then you may decide to pass a const reference instead of a value to save the copy operation.

In this answer there's a detailed example (that, incidentally, is also talking about rectangles) of the kind of very subtle bugs that you may run into by passing references when values should have been used instead. And note also that const-correctness is not going to be of any help when dealing with aliasing and lifetime issues.

Best practice to me regarding handling object access and representation is to use this order:

  1. Use plain objects and references unless you really can't.
  2. Go for smart pointers unless you really can't.
  3. Use plain pointers, you should be really know what are you doing if you are this far.

This applies, to definition of object within code and class members, and passing them to and from functions. Basically everywhere.

And by "really can't" I mean it is impossible to write a compilable code. I do not think there are any real overheads that would make sense to change this order in current state of compilers, the standard, and their optimization capabilities.

A little explanation.

Before people had troubles with returning variables, now we got RVO and NRVO. Also c++11 gives std::function , lambdas, and move semantics. IMO all of this is an effort to be able to use objects and references with no overhead over pointers, and this is to make more maintainable, readable and better structured code.

Sometimes you need to use pointers, eg in case of abstract class interfaces. Smart pointers help with object management and should be a default go-to in such cases. They for example handle deletion of the objects, so you can use them safely with standard containers with out extra effort. It is also harder to " double free " them, or easier to spot mistakes.

However, I saw one guy on SO implementing their smart pointers on some game engine. They could not use blessing of standard library, and had to make their own. This is when you use plain pointers.

I would adjust SetDimensions and GetDimensions to:

// immutable access:
const Rectangle & GetDimensions() const;

// mutable access:
Rectangle & GetDimensions();

// or instead of the mutable GetDimensions():
void SetDimensions(const Rectangle & newDimensions);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM