3
\$\begingroup\$

Background

In C++ unlike other OOP languages, we do not have a Base Class from where All Classes are derived. I understand in MFC and pre STL, some Compilers did had a similar model, so lets leave it out of the discussion and focus on what the standard provides.

In C++11, auto was introduced to derive the type from rvalue just another way of type deduction of templates. This was a huge relief when one has to write long list of types with all template parameters.

Using Naked Pointers were prone to errors so Smart Pointers were introduced as a remedy for the unavoidable evil.

Use Case

I have a function which should return something that would indicate Null Object when no data was found or if it found needs to return the data. I would like to know, if C++11 has an elegant way of writing the below code, considering, the template types are painful to read.

Code

std::unique_ptr<std::tuple<tstring, int, tstring> > CCOMErrorHandler::PopError()
    {
    unique_ptr<std::tuple<tstring, int, tstring> > error = nullptr;
    if (!m_lstErrors.empty())
        {
        error = std::unique_ptr<std::tuple<tstring, int, tstring> >(new std::tuple<tstring, int, tstring>(m_lstErrors.front()));
        if (error)
            {
            m_lstErrors.pop_front();
            }
        }
    return(error);
    }

One option is to use a typedef or macro replacement, but generally am not a big FAN as that keeps polluting namespace though you may have to use to few times in your code often only once.

\$\endgroup\$
1
  • \$\begingroup\$ Does std::optional serve your purpose nowadays? \$\endgroup\$ Commented Feb 6, 2021 at 13:22

2 Answers 2

5
\$\begingroup\$

The first question is, why are you using pointers in the first place here? Allocation with new should only be used when required. Perhaps it is in this case, but I'd rethink your design. Are the errors polymorphic? If so, why? If not, why do you need to use pointers?

Many STL containers use either an error-code approach, that is, they return a std::pair<T, bool>, where the bool represents success/failure, or they deal directly with iterators, and return an iterator to end() in the case of failure. Depending on the type of m_lstErrors, this could be a possibility (although obviously this won't work for a container adapter like std::stack).

Though it is not contained in the C++ standard library at the moment (it is slated for inclusion in C++14), std::optional was basically made for this purpose. In fact, it can be written using only C++11 features (and there's a reference implementation you could use here). Boost also contains a boost::optional type, although this lacks things such as move semantics and safe (explicit) bool conversion (although other than that, it is very similar in nature). This would change the code to look like this:

std::optional<std::tuple<tstring, int, tstring>> CCOMErrorHandler::PopError()
{
    typedef std::tuple<tstring, int, tstring> tuple_type;
    std::optional<tuple_type> error;
    if(!m_lstErrors.empty()) {
        error = *m_lstErrors.front();
        m_lstError.pop_front();
    }
    return error;
}

cppreference has an API reference for std::optional.

As for the complaint about template types, you can always wrap them in another namespace to help prevent pollution:

namespace mynamespace
{
namespace detail
{
    typedef std::tuple<tstring, int, tstring> error_type;
    typedef std::optional<error_type> optional_error;
}

detail::optional_error CCOMErrorHandler::PopError()
{
    //...
}
\$\endgroup\$
1
  • \$\begingroup\$ +1: For introducing me to std::optional, and reminding me to use namespace. The first question is, why are you using pointers in the first place here? Just, to have a unified return irrespective of presence or absence of data.Pointers works well here. Various containers have their own error-code approach, but generally (ex. for sequences) I would have to end up returning iterators, and may not always be valid outside the context (as in my example). Unless std::optional was there in the stdlib, I don't see extra mileage in using it from what I have now. \$\endgroup\$
    – Abhijit
    Commented Sep 25, 2013 at 16:31
1
\$\begingroup\$

I feel like you're facing a conundrum but you painted yourelf in that corner. You have a function that you specifically called PopError and then you realize that it may not pop an actual error because there may be no error to pop. You're trying to force a different style from how you're expected to do. I would suggest the following :

bool CCOMErrorHandler::HasError()
{
    return !m_lstErrors.empty();
}

auto CCOMErrorHandler::PopError()
{
    auto error = *m_lstErrors.front();
    m_lstError.pop_front();
    return error;
}

Confirming you got a "null" object or whether something exists to begin with may feel the same but I'd say the second way is the way you're expected to program in C++ because that's the API you get from the STL and you will have the same amount of code anyway.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.