C ++中“Nil”的概念

时间:2015-04-09 18:29:10

标签: c++ null

你记得在算法的本科讲座中,有一个Nil的概念是非常方便的,任何东西都可以分配或比较。 (顺便说一下,我从未在计算机科学专业读过本科生。)在Python中,我们可以使用None;在Scala中有Nothing(如果我正确地理解它,这是所有事物的一个子对象)。但我的问题是,我们怎样才能在C ++中拥有Nil?以下是我的想法。

我们可以使用Singleton Design Pattern来定义单个对象,但我目前的印象是,大多数人会在想到这一点时畏缩。

或者我们可以定义全局或静态。

我的问题是,在这两种情况中,我都无法想到能够将任何类型的任何变量分配给Nil,或者能够比较任何类型的任何对象的方法与Nil。 Python的None很有用,因为Python是动态类型的; Scala的Nothing(不要与Scala Nil混淆,这意味着空列表)优雅地解决了这个问题,因为Nothing是一切的子对象。那么在C ++中有一种优雅的Nil方式吗?

6 个答案:

答案 0 :(得分:10)

不,C ++中没有通用的nil值。逐字,C ++类型是不相关的,不共享共同的值。

您可以使用继承来实现某种形式的可共享值,但您必须明确地这样做,并且只能对用户定义的类型执行此操作。

答案 1 :(得分:9)

您描述为Nil的两个相关概念:unit typeoption type

单位类型

这是NoneType在Python中的含义,而nullptr_t在C ++中,它只是一种特殊类型,它具有传达此特定行为的单个值。由于Python是动态类型的,因此可以将任何对象与None进行比较,但在C ++中我们只能为指针执行此操作:

void foo(T* obj) {
    if (obj == nullptr) {
        // Nil
    }
}

这在语义上与python的相同:

def foo(obj):
    if foo is None:
        # Nil

选项类型

Python没有(或需要)这样的功能,但所有ML系列都有。这可以通过boost::optional在C ++中实现。这是类型安全封装的概念,即特定对象可能具有值或不具有值。这个想法在函数族中比在C ++中更具表现力:

def foo(obj: Option[T]) = obj match {
    case None => // "Nil" case
    case Some(v) => // Actual value case
}

虽然在C ++中也很容易理解,但是一旦你看到它:

void foo(boost::optional<T> const& obj) {
    if (obj) {
        T const& value = *obj;
        // etc.
    }
    else {
        // Nil
    }
}

这里的优点是选项类型是一种值类型,您可以很容易地表达任何“价值”(例如,您的optional<int*>可以将nullptr存储为值,与“零”)。此外,这可以适用于任何类型,而不仅仅是指针 - 只需要为增加的功能付费。 optional<T>将大于T并且使用起来更加昂贵(尽管只有一点点,尽管这一点很重要)。

A C ++ Nil

我们可以将这些想法结合起来,实际在C ++中创建Nil,至少有一个用于指针和选项。

struct Nil_t { };
constexpr Nil_t Nil;

// for pointers
template <typename T>
bool operator==(const T* t, Nil_t ) { return t == nullptr; }

template <typename T>
bool operator==(Nil_t, const T* t ) { return t == nullptr; }

// for optionals, rhs omitted for brevity
template <typename T>
bool operator==(const boost::optional<T>& t, Nil_t ) { return !t; }

(请注意,我们甚至可以将其概括为实现operator!

的任何内容
template <typename T>
bool operator==(const T& t, Nil_t ) { return !t; }

,但最好将自己局限于明确的案例,我喜欢指针和选项给你的明确性。

因此:

int* i = 0;
std::cout << (i == Nil); // true
i = new int(42);
std::cout << (i == Nil); // false

答案 2 :(得分:3)

C ++遵循的原则是你不会为你不能使用的东西买单。例如,如果您希望32位整数存储全部范围的32位值一个关于它是否为Nil的附加标志,则这将占用超过32位的存储空间。当然,你可以创建聪明的类来表示这种行为,但它不可用&#34;开箱即用&#34;。

答案 3 :(得分:3)

如果不依赖于封装类型的容器(例如boost::optional),则不能在C ++中使用它。

确实,有一些原因,因为C ++是:

  • 静态类型语言:一旦你定义了一个带有类型的变量,它就会粘在那个类型上(这意味着你不能分配一个值None,它不是每个类型可以代表的值,你可以做什么在其他动态类型的语言中,如Python)
  • 具有内置[1]类型的语言,并且没有针对其类型的公共基础对象:在公共对象中,您不能具有None的语义加上“此类型可以表示的所有值”适用于所有类型。

[1] 与Java内置类型不同,它们都具有从java.lang.Object继承的相应类。

答案 4 :(得分:2)

在大多数语言中,变量不是作为对象的名称实现的,而是作为对象的句柄实现的。

这样的句柄可以设置为&#34;什么也不保留&#34;只需很少的额外开销。这类似于C ++指针,其中nullptr状态对应于&#34;指向什么&#34;。

这些语言经常在垃圾收集方面做得很充分。垃圾收集和强制间接引用数据在实践中都有显着的性能提升,它们限制了您可以执行的操作。

当您在这些语言中使用变量时,您必须先按照指针&#39;得到真实物体。

在C ++中,变量名通常是指有问题的实际对象,而不是对它的引用。在C ++中使用变量时,可以直接访问它。一个额外的状态(对应于&#34;没有&#34;)将需要在每个变量中额外的开销,而C ++的一个原则是你不会为你使用的东西付出代价。

有一些方法可以制作一个可空的&#34; C ++中的数据类型。这些不同于使用原始指针,使用智能指针或使用std::experimantal::optional之类的东西。所有这些都有&#34;没有任何东西&#34;,通常可以通过获取对象并在bool上下文中进行评估来检测。要访问实际数据(假设它存在),您可以使用一元*->

答案 5 :(得分:-1)

在C ++ 11中,有一个值nullptr,可以分配给任何指针类型。

如果它不是指针类型,它必须在那里。对于特定的值,您可以定义特殊的&#34;我不存在&#34;价值观,但没有通用的解决方案。

如果可能不存在,您的选择是:

  • 指向它并使用nullptr表示不存在的值,或
  • 保留一个单独的标志来表示存在或不存在(这是boost::optional为你做的事),或
  • 为此特定用法定义一个特殊值,以表示不存在的值(请注意,这并非总是可行。)