How do I return an object from a function?

Consider the following scenario: There is a class CDriver

that is responsible for listing all connected output devices (represented by the class COutput

). The code for this might look something like this:

class COutput
{
    // COutput stuff
};

class CDriver
{
public:
    CDriver();  // enumerate outputs and store in m_outputs

    // some other methods

private:
    std::vector<COutput> m_outputs;
};

      

Now CDriver

should give the user access to the listed COutput

s.

The first way to achieve this is to return a pointer:

const COutput* GetOutput(unsigned int idx) const 
{ 
    return idx < m_outputs.size() ? &m_outputs[idx] : nullptr; 
}

      

As I can see this method presents the problem that if the pointer is stored by the user and persists after the object has CDriver

been destroyed, it is now a dangling pointer. This is because the pointee ( COutput

) object was destroyed during the object's destructor CDriver

.

The second way to do it is to return by reference:

const COutput& GetOutput(unsigned int idx) const 
{ 
    return idx < m_outputs.size() ? &m_outputs[idx] : m_invalidOutput; 
}

      

Here are the same problems as in the pointer approach. In addition, it has an additional caveat about not returning a real invalid object. If a is nullptr

returned as a returned pointer, it is obviously "invalid". However, when it comes to links, there is no equivalent nullptr

.

Go to the third number. Return by value.

COutput GetOutput(unsigned int idx) const 
{ 
    return idx < m_outputs.size() ? &m_outputs[idx] : m_invalidOutput; 
}

      

Here, the user doesn't need to worry about the lifetime of the returned object. However, the object COutput

must be copied and there is a similar reference approach, there is no intuitive way to check for errors.

I could go on ...

For example, objects COutput

can be allocated on the heap and stored in std::shared_ptr

and returned as such. This, however, will make the code very verbose.

Is it possible to intuitively solve this problem and not introduce unnecessary verbosity of the code?

+3


source to share


3 answers


Let me start by saying that you should absolutely not start messing with shared_ptr

in order to fix this problem. Just don't do it. You have several different options, which are reasonable.

First, you can simply return by value. If COutput

not enough, this is a good way. To deal with an out-of-bounds index, you have two options. One of them is the exception. Works well and is easy. This is what I would recommend here, most likely. Make sure the user has an item size()

that the user can summon to get the size, so they can avoid paying the cost of the throw if it's too expensive for them. You can also refund optional

. It's in the standard library as of 17, forcing up to, and standalone implementations exist.

Second, you can return by pointer / link. Yes, it can hang out. But C ++ does not claim to protect against this. Every standard container has methods begin()

and end()

that return iterators can dangle easily too. Expecting customers to avoid these errors is not unreasonable in C ++ (you should, of course, document them).

Third, you can do an inversion of control: instead of giving the user an object to work with, you instead make the user navigate to the action they want to take. In other words:

template <class F>
auto apply(std::size_t idx, F f) const 
{
    if (idx >= m_outputs.size())
    {
        throw std::out_of_range("index is out of range");
    }

    return f(m_outputs[idx]); 
}

      



Using:

CDriver x;
x.apply(3, [] (const COutput& o) {
    o.do_something();
});

      

The user needs to work a little harder to get something to dangle in this case (although it is still possible) since they are not handed a pointer / link and you don't need to make a copy either.

You can, of course, change in apply

different ways; for example not returning from a function call, but instead returning true / false to indicate if the index was in a range rather than throwing. The basic idea is the same. Note that this approach has to be modified for use in conjunction with virtual functions, making it less desirable. Therefore, if you are thinking about polymorphism for CDRiver, you should consider this.

+5


source


Take a look at C ++ 11 shared pointers. With shared pointers, the base object's deconstructor will not be called until all shared pointers "owning" that object have been destroyed. This removes a lot (but not all) of the headache when dealing with multiple references to the same object.



Here's some more info: http://en.cppreference.com/w/cpp/memory/shared_ptr

0


source


1). Tested and Tried: Enter the default argument argument

2) You can use tuples and std :: tie .

const std::tuple<bool, COutput> GetOutput(unsigned int idx) const 
{ 
     return idx < m_outputs.size() 
               ? std::make_tuple(true m_outputs[idx])
               : std::make_tuple(false,  m_invalidOutput); 
}

bool has_value;
COutput output;

std::tie(has_value, output) = GetOutput(3);

      

To replace tuples and std :: tie in C ++ 17 structured bindings can be used.

3) C ++ 17 will be std :: optional for this kind of scenario.

0


source







All Articles