从指针升级到引用——如何处理 NULL 到引用

Upgrade from pointers to references - How to deal with NULL to reference

我是 C++11 的新手,我 class 是这样的:

class Pair; // defined somewhere...

class IReadOnlyList{
public:
        virtual const Pair *get(const char *key) const = 0;

        inline const Pair *operator[](const char *key) const{
                return get(key);
        };

        inline bool exists(const char *key) const{
                return get(key) != NULL;
        };
};

它工作正常,但我想删除指针。但是,如果我改变 Pair *get()Pair &get,那么我无法处理不存在的对。

选项是-
1. 例外
2. 空对象。因为Pair是POD(标准布局),我完全可以用这个
3. return 封装 Pair * 的中间对象 - 听起来很蠢
4. 保留 NULL :)

有没有我遗漏的选项?

尽可能避免指针。

在这种情况下,函数 可能 对 return 没有有意义的值,那么 boost::optional 可能是一种解决方案:

virtual boost::optional<Pair> get(const char *key) const = 0;

Return 当您对 return 没有任何有意义的值时 boost::optional<Pair> 的空实例。调用者需要检查 returned boost::optional<Pair> 是否为空或持有实例 Pair.


此外,在这种情况下,find 似乎比 get 更好——因为函数可能会或可能不会找到与关联的 key 相关的有意义的值。

//return empty optional when no value found
virtual boost::optional<Pair> find(const char *key) const = 0;

如果你想保留 get 作为函数名,那么抛出异常是更好的解决方案:

//throw exception when no value found
virtual const Pair const& get(const char *key) const = 0;

您可以在代码中同时使用这两个函数 — get 可以根据 find.

来实现

希望对您有所帮助。

我认为期望是要走的路。由于某些原因,人们低估了他们的需求。

如果你无论如何都不能抛出异常,你可以将结果作为引用传递,并且 return bool 来指示函数结果——如果它是真的——作为引用传递的对象是有效的结果,如果为 false- 处理失败并且不使用该对象。

bool get(const char *key, Pair& pair) const{
   if (good){
     pair = goodObject;
     return true;
   }
   return false;
}

但同样,使用例外。

你能 return 按值对吗?因此,值将在堆栈上或任何地方创建,您不必担心指针泄漏 之类的。

// Interface
Pair get (const char* key) const = 0;

... 

// Usage
Pair result = irp.get();

result.doSomething();

因此,您的 get 看起来像:

   Pair IRPointerClass::get (const char* key) 
   {
      Pair result;
      ... initialize result using key...
      return result;
   }

还有另一种方法——使用空对象。 在不知道 Pair class 的内部数据成员的情况下,无法准确指出 Pair 的哪个内部数据成员将用于此目的,但在一个项目中我们使用了这种声明 public 静态常量对象的方法(例如Pair) 表示错误或无效的情况。 调用者可以检查该对象的某些内部数据成员(或调用某些安全方法 returning bool)以确定该对象是否合法。 它不像异常那样优雅,也不像 NULL 指针那样干净,但您始终可以 return 一个有效的对象引用,并且也消除了调用者是否处理了异常的恐惧。

由于您return引用(指针)而不是(!)值:

  1. 例外情况: 不好,如果不存在是有效(非异常)return 值。

  2. 空对象: 正如你提到的,一个笨拙的解决方法。

  3. Return 中间对象,封装了结果: 将是一种方法(参见 boost::optional),但您 return 一个参考(指针)

  4. 保留 nullptr: 在这种情况下简单而聪明。不测试 null undefined 的 return 值(不遵守合同)

我认为没有比 4 更聪明的了。)

除了保留指针和做一些愚蠢的事情,这里有我能想到的所有带有中间容器的选项class。

为了能够编译,我创建了虚拟 Pair class 和函数而不是 get() 方法。

注意:我没有尝试在这里处理内存泄漏 - 请不要post评论...

#include <stdio.h>

// This is Pair class

class Pair{
public:
    void print() const{
        printf("Hello\n");
    }
};

// This is Pair Container class,
// will be interesting to be rewritten with templates

class _Pair{
public:
    _Pair(const Pair *p = NULL) : _pair(p){};

    inline const Pair & val() const{
        return *_pair;
    }

    inline explicit operator bool() const {
        return _pair != NULL;
    }

    inline const Pair & operator *() const {
        return *_pair;
    }

    inline const Pair & operator ()() const {
        return *_pair;
    }

    inline const Pair * operator ->() const {
        return _pair;
    }

private:
    const Pair *_pair;
};

// this is GET() method

const _Pair & getPair(bool a){
    static const _Pair pnull = _Pair();

    if (a){
        return *new _Pair( new Pair() );
    }

    return pnull;
}

// this is usage

int main(int argc, char** argv)
{
    const _Pair & p = getPair(false);

    if (p){
        p().print();
    }

    const _Pair & q = getPair(true);

    if (q){
        // option 1: nice, but additional ()
        // same as boost::optional
        (*q).print();

        // option 2: too long
        const Pair & pp = *q;
        pp.print();

        // option 3: no difference from pointer
        q->print();

        // option 4: using method - looks not bad
        // same as boost::optional
        q.val().print();

        // option 5: nive and yummy :)
        q().print();
    }

    return 0;
}

我确实检查了 boost::optional,然后我检查了 std::optional 的(未来)实验规范,并得出以下结论:

https://github.com/nmmmnu/HM3/blob/master/std/std_container.h

https://github.com/nmmmnu/HM3/blob/master/std/std_optional.h

然后因为我会经常用到它,在定义Pair的地方,我也定义了:

class OPair : public std_optional<const Pair>{
public:
    OPair(const Pair *pair = nullptr) : std_optional(pair){};
};

然后 IList::get() 看起来像这样:

const OPair IArray::get(const char *key) const override{
    uint64_t index;
    if (lookup(key, & index))
        return nullptr;  // LINE 1

    return _data[index]; // LINE 2
}

LINE 1 为NULL,已自动转换为Pair *,然后转换为OPair。 LINE 2 正在返回 Pair *,已自动转换为 OPair。

使用 IList 看起来像:

OPair p = list.get("bla");
if (p){
   printf("%s\n", p->getVal());
   //or
   printf("%s\n", p().getVal());
   //or
   printf("%s\n", p.val().getVal());
}