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
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?
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)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.DataClass &
doesn't seem to make sense, especially when I'm storing pointers (heap vs. stack allocation).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;
};
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.
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 foroperator []
, because I can only access the raw pointer of anstd::unique_ptr
using the smart pointer'sget()
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 foroperator []
.
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];
}
// ...
};
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