0

My class has methods that return const <Container>& because I don't want the returned container to be modified outside and copying could be expensive. for Example: const std::set<ClassA>& foo() and I want foo() to be able to return a const reference to an empty std::set<ClassA> if it has to. i.e.

const std::set<ClassA>& foo(const std::string& key) {
    std::map<std::string, std::set<ClassA>>::iterator itr = m_elements.find(key);
    return (itr != m_elements.end()) ? *itr : /*empty std::set<ClassA>*/;
}

But I cannot really return a const reference to a temporarily constructed empty std::set<ClassA> in foo(). To solve this I am defining a generic template singleton class in a common place so that it can be used with any type

template <typename T> class cNull
{
  public:
    static const T& Value() {
      static cNull<T> instance;
      return instance.d;
    }
  private:
    cNull() {};
    cNull(const cNull<T>& src);
    void operator=(cNull<T> const&);
    T d;
};

So now foo() can be something like

const std::set<ClassA>& foo(const std::string& key) {
    std::map<std::string, std::set<ClassA>>::iterator itr = m_elements.find(key);
    return (itr != m_elements.end()) ? *itr : cNull<std::set<ClassA> >.Value();
}

What I was wondering is, if there's a better way to solve this problem and if there are any issues with this design?

Chenna V
  • 10,185
  • 11
  • 77
  • 104
  • 1
    Why do you **have to** return a reference? – Shoe Nov 22 '13 at 15:38
  • Why not just have a class-level variable m_emptySet and return reference to that? (It's just as likely to be modified by caller as the data sets in m_elements but that wasn't part of question) –  Nov 22 '13 at 15:40
  • 1
    If you **have** to return a reference then your options are limited, if you make the return type a pointer then you can return a NULL no problem (references imply there is an extant value, pointers imply the value might exist) I take it there is a requirement to return a reference at present? – GMasucci Nov 22 '13 at 15:43
  • @Jeffrey: The reason I want to return a const reference is because I don't want to copy the container and I don't what it to be modified outside – Chenna V Nov 22 '13 at 16:20
  • @ebyrob having a class variable is a waste of space, because that would create an empty set per class object. Having a singleton would create only one empty set and be used by all classes. – Chenna V Nov 22 '13 at 16:33
  • @blueskin ya, ok. Make it static. If you already know the answer why are you asking? –  Nov 22 '13 at 18:46
  • @ebyrob, well as I already mentioned in my question, I was looking for alternatives and any issues with this design. Thanks – Chenna V Nov 25 '13 at 17:48
  • @blueskin personally, I definitely wouldn't write a template class. Templates are hard to understand, especially when you consider how it will affect the code footprint later on. If you've got 20 classes using one internal data type, it might make sense to have a central singleton with named static instances that can be called upon for return as special values. For one thing what if you suddenly need another constant, say "MinValue" or "N/A" instead of "Null"? Much easier to say (and read) `Consts::NullSetClassA` than `cNull >.Value()` in my book. –  Nov 25 '13 at 18:54

5 Answers5

1

In such situation, you've usually two alternatives:

  • Either you throw exception if no element is found.
  • Or return some object like boost::optional which might contain the found element.
Nawaz
  • 353,942
  • 115
  • 666
  • 851
1

What I was wondering is, if there's a better way to solve this problem and if there are any issues with this design?

Sure there is: don't return a reference but a copy. When you say that "you want to be able to return an empty std::set<T>" you are just stating that you may want to change the value returned in respect to it's original state (as a member variable). A copy is perfectly fine in this case.

Shoe
  • 74,840
  • 36
  • 166
  • 272
1

Depends a little on why you're returning a reference.

If it is because the caller expects to retain the reference and have it reflect changes made via the object on which foo was called, then what happens if you return a reference to your singleton empty set, and the same key is later added to m_elements? The reference doesn't do what it's advertised to do. It might be better to add an empty set to the map when foo is called, so the code becomes:

const std::set<int>& foo(const std::string& key) {
    return m_elements[key];
}

If you're returning a reference solely for performance reasons (to avoid a potentially expensive copy of a large set), not because you actually want the semantics of a reference to a part of the object, then returning a static empty set will work, and I suppose there's nothing wrong with having a template helper to achieve it if you find yourself doing the same thing often with several types. Be very careful to document, though, that the reference returned may or may not reflect further changes to the object (according to whether key was present when foo was called, although you might not want to guarantee that). Then callers will know to avoid using it past its sell-by date, which is whenever anyone next makes a relevant change to the object.

If you don't know why you're returning a reference, then either return a copy or else work out what it is that callers are expected to do with this set, and replace foo with one or more functions that does it. That way, you don't allow references to your class's innards to fall into the hands of users.

I've assumed that the empty set is correct for some good reason -- perhaps to avoid every caller having to test the return value and/or check for the existence of key before calling foo.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
0

There are several options for doing this, but before you can pick one, you have to answer: Do I really need to return a reference?

If you are trying to operate on the result (and want it reflected in the changes - and cannot otherwise change the interface), the answer is yes. If any one of those conditions changes, you can return by copy, which makes this much easier:

std::set<int> foo(const std::string& key) const
{
    std::set<int> results;
    std::map<std::string, std::set<int>>::iterator it = m_elements.find(key); // assuming m_elements is std::map<std::string, std::set<int>>
    if (it != m_elements.end())
    {
        results = it->second;
    }
    return results;
}

If you must return a reference, you can do something like boost::optional (or use std::pair to simulate it), throw an exception, have an internal null-set. The option that is closest to what you are attempting to do is the optional/pair approach:

std::pair<bool, std::set<int>*> foo(const std::string& key)
{
    std::pair<bool, std::set<int>*> results = std::make_pair(false, nullptr);
    std::map<std::string, std::set<int>>::iterator it = m_elements.find(key);
    if (it != m_elements.end())
    {
        results.first = true;
        results.second = &(it->second);
    }
    return results;    
}

Then you can check the boolean value (guaranteed to be valid) to see if the pointer value is valid. boost::optional does something similar (and if you can use boost, prefer it over using the std::pair version to simulate it).

Zac Howland
  • 15,777
  • 1
  • 26
  • 42
  • "Do I really need to return a reference?" : Yes. because copying a std::set could be expensive and the objects in the set need to be copyable objects. – Chenna V Nov 22 '13 at 16:40
  • "Could be expensive" - that depends on how big your set is. A set of 100 integers is not going to be expensive to copy ... a set of 1,000,000 integers could be. The easiest solution to your problem if you insist on not copying the set is to return a `std::pair*>` instance. – Zac Howland Nov 22 '13 at 16:58
  • Zac, sry, I shouldn't have given set as an example. so I changed the question, what I have is a set and ClassA is not copyable. Thanks for your solution – Chenna V Nov 22 '13 at 17:05
  • If `ClassA` is not copyable, you cannot put it in a `std::set` anyway: Section 23.2.1 paragraph 4 (in the table): `Requires: T is CopyInsertable into X (see below). post: a == X(a).` Section 23.2.1 paragraph 13 defines CopyInsertable as `allocator_traits::construct(m, p, v);` is well-formed (basically meaning it can call the copy-constructor). – Zac Howland Nov 22 '13 at 17:17
0

I would return a pointer, or nullptr to denote not found.

const std::set<ClassA>* foo(const std::string& key) 
{
    auto it = m_elements.find(key);
    if (it == m_elements.end()) return NULL;
    return &(*it);
}

simple, and it doesn't suffer from singletonitis.

Grim Fandango
  • 2,296
  • 1
  • 19
  • 27