0

我的班级有返回的方法,const <Container>&因为我不希望返回的容器在外部被修改,并且复制可能很昂贵。例如:如果必须的话const std::set<ClassA>& foo(),我希望foo()能够返回对空的 const 引用std::set<ClassA>。IE

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>*/;
}

但我不能真正返回对临时构造的空std::set<ClassA>in的 const 引用foo()。为了解决这个问题,我在一个公共位置定义了一个通用模板单例类,以便它可以与任何类型一起使用

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;
};

所以现在foo()可以像

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();
}

我想知道的是,是否有更好的方法来解决这个问题,以及这个设计是否有任何问题?

4

5 回答 5

1

在这种情况下,您通常有两种选择:

  • 如果未找到任何元素,则抛出异常。
  • 或者返回一些可能boost::optional包含找到的元素的对象。
于 2013-11-22T15:37:41.560 回答
1

取决于您返回参考的原因。

如果是因为调用者希望保留引用并让它反映通过foo被调用的对象所做的更改,那么如果您返回对单例空集的引用,并且稍后将相同的键添加到 会发生m_elements什么?参考并没有做它宣传的事情。调用时向映射添加一个空集可能会更好foo,因此代码变为:

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

如果您仅出于性能原因返回引用(以避免大型集合的潜在昂贵副本),而不是因为您实际上想要对对象一部分的引用的语义,那么返回静态空集将起作用,我想如果你发现自己经常用几种类型做同样的事情,那么有一个模板助手来实现它并没有错。但是,要非常小心地记录返回的引用可能会或可能不会反映对对象的进一步更改(根据调用key时是否存在foo,尽管您可能不想保证这一点)。然后,调用者将知道避免在过期日期之后使用它,即任何人接下来对对象进行相关更改时。

如果你不知道为什么要返回一个引用,那么要么返回一个副本,要么计算出调用者应该用这个集合做什么foo,并用一个或多个执行它的函数替换。这样一来,您就不会允许对您班级内部的引用落入用户手中。

我假设空集是正确的,出于某种充分的理由——也许是为了避免每个调用者都必须测试返回值和/或key在调用之前检查是否存在foo.

于 2013-11-22T16:11:29.590 回答
1

我想知道的是,是否有更好的方法来解决这个问题,以及这个设计是否有任何问题?

当然有:不要返回参考而是副本。当您说“您希望能够返回一个空的std::set<T>”时,您只是在说明您可能想要更改返回的值相对于它的原始状态(作为成员变量)。在这种情况下,副本非常好。

于 2013-11-22T15:47:53.550 回答
0

我会返回一个指针,或者 nullptr 来表示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);
}

很简单,而且不会患单胎炎。

于 2013-11-22T17:57:02.303 回答
0

这样做有多种选择,但在您选择一个之前,您必须回答:我真的需要返回参考吗?

如果您尝试对结果进行操作(并希望它反映在更改中 - 并且不能以其他方式更改界面),答案是肯定的。如果这些条件中的任何一个发生变化,您可以通过副本返回,这使得这更容易:

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;
}

如果你必须返回一个引用,你可以做类似的事情boost::optional(或std::pair用来模拟它),抛出一个异常,有一个内部的空集。最接近您尝试做的选项是可选/配对方法:

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;    
}

然后可以检查布尔值(保证有效),看看指针值是否有效。 boost::optional做类似的事情(如果你可以使用 boost,比起使用std::pair版本来模拟它更喜欢它)。

于 2013-11-22T15:50:00.360 回答