在C++中,我们需要什么才能始终使用引用而不是指针?

3
根据我在网络上找到的信息,你应该尽可能使用引用,除非必须使用指针。就我所理解的,使用指针的唯一原因是:
  • 处理原始内存或者
  • 可能需要返回null或者
  • 处理从new()返回的新分配的对象。

第一个原因实际上无法处理。这是任务的固有特性。 第二个原因可以通过Null对象模式处理,并返回对该Null对象的引用。这要求您定义每个要能够引用为Null的类实例的Null对象。 第三个原因可以使用智能指针处理。

我的疑问是:是否可能在完全不考虑指针的情况下“用Java方式编写C ++”并使用引用进行编程?是否可能像Java一样在C ++中按语言定义一个null对象?

如果问题陈述不清或是FAQ,请谅解,我对C ++很生疏,必须开始一项新的项目学习一些新东西,并适应不同的思考方式。


3
第二个可以用 boost::optional 来实现。 - Benjamin Lindley
2
关键在于 Java 更像总是使用指针,但使用点符号表示法,而 C++ 使用“->”符号(因为引用时使用点符号表示法)。我的意思是,在 Java 中,您有一个空引用,“==”比较地址,而在 C++ 中,“==”与 Java 中的“.equals()”相同。 - akappa
4
实际上,Java的引用行为更像C++指针而不是C++引用,因为它们可以为空、可以重新分配,并且比较它们时会比较地址而不是对象。所以唯一缺少的就是指针算术。 - Grizzly
1
@Grizzly:这取决于你所说的“不使用指针”的含义。如果我写std::shared_ptr<Foo> myfoo(new Foo);,我的代码中没有指针类型,但当然有一个带有指针类型的子表达式。如果我们从shared_ptr中去掉operator->,只使用operator*,那么我们可以使用智能指针而不必直接使用指针,因此除了在第一次创建它们时,我们不需要“使用指针”来使用智能指针。这是一种品味问题,你是否认为这是“不使用原始指针”,但正如Kerrek所说,你并没有明确地使用它们。 - Steve Jessop
4
在C++中,引用的目的是支持按引用调用、按引用返回和按引用捕获。几乎从不将其用于其他任何目的。许多初学C++的程序员认为C++引用类似于Java引用或C#引用,但实际上它们并不相同。在C ++中,T&类似于C#中的ref T,而不是C#中的类引用。如果想要类似于C#引用的东西,请使用智能指针,例如std::shared_ptr<T>std::unique_ptr<T>,具体取决于所需的所有权语义。 - fredoverflow
显示剩余6条评论
5个回答

7

你无法重新定位一个引用,但你可以重新定位指针以指向新的变量。这种行为在引用中无法模拟(引用始终绑定到它们初始化的变量上),而指针提供的便利性使得如果只有引用而没有指针的话,使用C ++几乎是不可能的。


如果你限制自己只使用数据表示,那么是的。但是,你完全可以使用函数来实现这一点。 - MSN
1
@MSN:您能详细说明一下您的评论吗?恐怕我不太理解。 - Alok Save
如果您不持久化引用,即将它们绑定到数据,那么您可以通过向函数传递不同的引用来“重新设置”它们。也就是说,重新设置引用只会影响长期存在的数据。 - MSN
@MSN,你所说的就像是函数式编程,其中总是有不可变的对象,我说得对吗? - Lol4t0
@Lol4t0,是的,很相似。这个想法是从输入中派生出你关心的状态,而不是将其持久化。 - MSN

7

需要什么?可重新绑定的引用,也称为指针。一旦绑定,引用将无法更改其所指对象。

struct Anchor{ /*some data*/ };

struct Sprite{
  void set_anchor(Anchor const& a){ _anchor = &a; }
  Anchor const* _anchor;
};

struct Entity{
  Anchror _anchor;
};

使用类似这样的方法,您可以通过更改其锚点来重新定位屏幕上的 Sprite。如果使用引用,您该如何实现?


哦...哇。我忘了这一点。你不能重新分配引用。一旦绑定,它们就会粘在那个对象上。C++0x中是否可能实现这一点? - Stefano Borini
2
@Kerrek:嗯...猜猜看那是如何实现的。 :) - Xeo
1
@Xeo:Haskell是一种纯函数式语言,它不提供像“赋值”和“重新绑定”这样的可变操作。或者至少,“纯”的代码不使用任何可变技巧。 - Steve Jessop
@Steve:啊。既然 C++ 不是纯函数式的,你会建议让它变成纯函数式来完全放弃指针吗? :) - Xeo
@Xeo:这是克服你反对意见的一种方法。我认为不是最好的方法,因为一旦你去除了副作用,C++ 的精髓就没多少了。 - Steve Jessop
显示剩余4条评论

2

你无法重新设置引用,也不能有空引用。Java称之为引用的实际上是指针。我不知道为什么你想要避免它们。在 C++ 中,引用具有特定的作用:它们允许使用与按值传递/返回相同的语法,而没有开销;它们还可以用于 inout 参数和公开对象的内部(例如,operator[] 经常返回引用)。除了作为函数参数或返回值外,它们应该相当少见。


在某种意义上,引用是允许您拥有对象的关键。想象一下,如果您想说T a; T b = a;... - Kerrek SB
@KerrekSB:我不明白你的评论,请详细说明一下? - fredoverflow
@FredOverflow:你需要为该构造函数编写一个拷贝构造函数才能使其自然工作,并且你只能使用引用来定义拷贝构造函数。 - Kerrek SB
@KerrekSB 的 C++ 类中有复制构造函数但没有引用,所以它们并不是必需的 ;) - fredoverflow
@FredOverflow根据当前标准的定义,如果没有引用的话,它就不会成为一个复制构造函数:-)。但毫无疑问,你不需要引用来复制对象。引用被引入的原因是语法:您不希望写&a+&b,以便传递指向重载operator+的指针。在那之后,赋予它们与指针不同的语义为它们打开了额外的用途。 - James Kanze

1

空对象模式真的很糟糕,你需要知道这一点。它并不是任何具有广泛影响力的解决方案。

指针需要可重新绑定,仅此而已。此外,Java中的“引用”实际上是C++中的“指针”。C++中的“引用”没有Java的等价物。


1
证明为什么/何时空对象模式真的很糟糕。我的意思不只是“不必要”(这很幼稚-糟糕),而是“真的很糟糕”? - sehe

-5

我过去使用类似于以下代码的方式重新分配了 C++ 引用:

struct EmptyType {};
const EmptyType Null;

template<typename DataT>
class DynamicReferenceT
{
    union
    {
        DataT & _ref;
        DataT * _ptr;
    };

public:
    DynamicReferenceT(DataT & r) : _ref(r) { }

    void operator=(DataT & r) { _ptr = &r; }
    void operator=(const EmptyType &) { _ptr = NULL; }
    DataT & operator()() { return _ref; }
};

class SomeClass
{
    int _value;

public:
    SomeClass(int val) : _value(val) {}
    int value() { return _value; }
};

int _tmain(int argc, _TCHAR* argv[])
{
    SomeClass objA1(100), objA2(200), objA3(300);

    // initially define the dynamic ref
    DynamicReferenceT<SomeClass> refObj(objA1);
    cout << "refObj = " << refObj().value() << endl;

    // reassign the dynamic ref
    refObj = objA2;
    cout << "refObj = " << refObj().value() << endl;

    refObj = objA3;
    cout << "refObj = " << refObj().value() << endl;

    // assign "null" to reference
    refObj = Null;

    return 0;
}

// output
100
200
300

在C++中,您无法重载.运算符,因此我重载了()运算符以“取消引用”动态引用。

这只是一个玩具示例 - 您应该进一步完善实现以处理对象的删除和复制。

更新
我向C++社区认输,这是我非常糟糕的C++示例,真正成为了如何滥用C++的好例子。实际上,除了我几年前在某个不愿透露名称的点卡公司继承的非常糟糕的遗留C++库中必须重新定义引用的情况外,我实际上并不提倡这种构造。


1
我认为通过一个成员将值存储在联合中,然后通过另一个成员检索会导致未定义的行为。 - Ferruccio
1
@hypercode 不,这不是坏事,而是打破语言障碍。使用它,您可以将任何内容放入对其他任何内容的引用中,从而打破了强制类型检查。当然,您使用模板包裹了它,但是为此打破了语言的类型系统。联合体是互斥的数据空间节省器,而不是绕过转换限制的聪明方法。如果指针用于删除对象,并且稍后使用该数据来实例化结构,则现在结构的前四个字节将充当对类型DataT的引用。 - Lee Louviere
3
如果这个可以编译通过,那么是时候更换编译器了。根据标准(§9.5/2),如果一个联合体包含非静态的引用类型数据成员,程序就是不合法的。 - James Kanze
1
@josefx,这完全没有关系。如果它未定义,那么它可能在一个版本的编译器上工作,并且在下一个版本上根本不起作用。对编译器进行更新以修复问题可以自由地删除对预期反射的支持。因此,未定义行为是一种不良实践。 - Lee Louviere
1
@Xaade 作为一个普遍的陈述是正确的。然而,gcc文档指出这是常见的,并保证它能够工作(即使设置-strict-aliasing标志也不会破坏它)。在这种情况下,你提到的静默后台更新会让很多用户感到恼火。 - josefx
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接