你记得在算法的本科讲座中,有一个Nil
的概念是非常方便的,任何东西都可以分配或比较。 (顺便说一下,我从未在计算机科学专业读过本科生。)在Python中,我们可以使用None
;在Scala中有Nothing
(如果我正确地理解它,这是所有事物的一个子对象)。但我的问题是,我们怎样才能在C ++中拥有Nil
?以下是我的想法。
我们可以使用Singleton Design Pattern来定义单个对象,但我目前的印象是,大多数人会在想到这一点时畏缩。
或者我们可以定义全局或静态。
我的问题是,在这两种情况中,我都无法想到能够将任何类型的任何变量分配给Nil
,或者能够比较任何类型的任何对象的方法与Nil
。 Python的None
很有用,因为Python是动态类型的; Scala的Nothing
(不要与Scala Nil
混淆,这意味着空列表)优雅地解决了这个问题,因为Nothing
是一切的子对象。那么在C ++中有一种优雅的Nil
方式吗?
答案 0 :(得分:10)
不,C ++中没有通用的nil
值。逐字,C ++类型是不相关的,不共享共同的值。
您可以使用继承来实现某种形式的可共享值,但您必须明确地这样做,并且只能对用户定义的类型执行此操作。
答案 1 :(得分:9)
您描述为Nil
的两个相关概念:unit type和option 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
并且使用起来更加昂贵(尽管只有一点点,尽管这一点很重要)。
我们可以将这些想法结合起来,实际在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)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;价值观,但没有通用的解决方案。
如果可能不存在,您的选择是:
boost::optional
为你做的事),或