Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessor functions for containers with std::unique_ptr

I'm about to design an API where two classes exist: A data/calculation class and a container for this class. The container, however, is not simply a dumb container but also carries information on the collection as a whole that goes beyond the usual stuff that can be implemented with iterators.

So, the container basically wraps a std::vector, adds some precomputed attributes, utility functions, etc. What the API of the container class should also contain is

  1. A method to add instances of the data/calc class,
  2. accessor methods.

I do not simply want to open the std::vector to the public, because the addition of a new data/calc class to the container causes the re-computation of a number of attributes of the collection class. I want to, however, provide accessor method equal to the STL container accessor methods at() and operator [].

Initially, I added a private attribute of the type std::vector<std::unique_ptr<DataClass>>. An addDataObject(DataObject *d) method exists that wraps the object in an unique_ptr and updates the statistics.

The problem, however, comes with the accessor methods: What would their return type be?

  • Simply returning std::unique_ptr<DataClass> & doesn't seem to be a clean API design: The user of the API does not need to know I use smart pointers. (See also Herb Sutter's presentation about C++11)
  • Returning DataClass *& isn't possible, especially not for operator [], because I can only access the raw pointer of an std::unique_ptr using the smart pointer's get() method, which does not give me a reference.
  • Returning DataClass & doesn't seem to make sense, especially when I'm storing pointers (heap vs. stack allocation).
  • Returning DataClass * would violate the princpile of least surprise, because if a method is named in the sense of the STL container accessor methods, one would expect it to return a reference, especially for operator [].

As an illustration, this would be a simplified example:

class DataClass
{
    Container *m_parent;
    /* ... other member attributes/parameters ... */
public:

     /* A method that calculates something based on the attributes: */
     double someComplicatedCalculation(const double &input);
};


class Container
{
     std::vector<std::unique_ptr<DataClass>> m_collection;

     /* more attributes: maintenance info, precomputed values, etc. */

  public:

      void addDataObject(DataClass *d);

      /* What would the right return type be? */

      DataClass *&at(const int &index);
      const DataClass *&at(const int &index) const;
      DataClass *&operator [](const int &index);
      const DataClass *&operator [](const int &index) const;
};
like image 461
Technaton Avatar asked Apr 26 '15 17:04

Technaton


Video Answer


2 Answers

Returning DataClass & doesn't seem to make sense, especially when I'm storing pointers (heap vs. stack allocation).

I disagree. That's what all the standard containers return. Unless the fact that you're using unique_ptr is something that should be visible to users (and I'm assuming it shouldn't be), then the path of least surprise would be to just do as the standard library do: return an lvalue reference to the object.

Additionally, returning a reference makes ownership clear. If you were to return a pointer, you'd have to indicate somewhere that the user must not delete it.

like image 76
Barry Avatar answered Sep 23 '22 08:09

Barry


Its always difficult when you don't know all the details but here is what I think:

  • Simply returning std::unique_ptr<DataClass> & doesn't seem to be a clean API design: The user of the API does not need to know I use smart pointers. (See also Herb Sutter's presentation about C++11)

Definitely yes.

  • Returning DataClass *& isn't possible, especially not for operator [], because I can only access the raw pointer of an std::unique_ptr using the smart pointer's get() method, which does not give me a reference.

Indeed.

  • Returning DataClass & doesn't seem to make sense, especially when I'm storing pointers (heap vs. stack allocation).

That does make sense. What I think may not make sense is storing pointers to begin with. A std::vector stores everything on the heap anyway so if that's the reason to use pointers, it doesn't apply.

  • Returning DataClass * would violate the princpile of least surprise, because if a method is named in the sense of the STL container accessor methods, one would expect it to return a reference, especially for operator [].

Indeed.

I would say you should return DataClass&. Probably you should not use pointers in your std::vector but if you must use them you can make your accessors like:

DataClass& operator[](std::size_t index)
{
    return *m_collection[index];
}

But better would be to not use pointers:

class Container
{
     std::vector<DataClass> m_collection;

    DataClass& operator[](std::size_t index)
    {
        return m_collection[index];
    }

    // ...
};
like image 45
Galik Avatar answered Sep 22 '22 08:09

Galik