在 C++ 中使用指针总是不好的吗?

Posted

技术标签:

【中文标题】在 C++ 中使用指针总是不好的吗?【英文标题】:Is using pointers in C++ always bad? 【发布时间】:2016-01-13 23:56:27 【问题描述】:

有人告诉我要避免在 C++ 中使用指针。似乎我无法避免它们,但是在我正在尝试编写的代码中,或者我可能错过了其他出色的 C++ 功能。

我希望创建一个包含另一个类 (class2) 作为数据成员的类 (class1)。然后我希望 class2 了解 class1 并能够与之通信。

我可以引用 class1 作为 class2 中的成员,但这意味着我需要在 class2 的构造函数中提供对 class1 的引用作为参数并使用我不想要的初始化列表。我正在尝试在不需要构造函数的情况下执行此操作。

我希望 class2 有一个名为 Initialise 的成员函数,它可以接收对 class1 的引用,但如果不使用指针,这似乎是不可能的。人们会在这里推荐什么?提前致谢。

代码完全简化,只是为了理解主要思想:

class class1

    public:
        InitialiseClass2()
        
            c2.Initialise(this);
        

    private:
        class2 c2;

;

class class2

    public:
        Initialise(class1* c1)
        
            this->c1 = c1;
        

    private:
        class1* c1;

;

【问题讨论】:

你是在问是否使用引用而不是指针,或者你是否不应该使用? 它的原始拥有指针是要避免的。 见Why are pointers not recommended when coding with C++? @Engineer999,你应该用勺子代替叉子吗?同问。指针是指针,引用是引用。它们有自己的适用性。 在嵌入式系统领域使用指针来访问硬件设备。在这种情况下,原始指针比智能指针工作得更好(更有效)。此外,没有分配“内存”,也没有删除。 【参考方案1】:

如果不使用指针,这似乎是不可能的

这是不正确的。实际上,要处理对其他对象的引用,将引用放入构造函数中:

class class2

    public:
        class2(class1& c1)
           : c1(c1)
        

    private:
        class1& c1;
;

这里的关键是初始化而不是分配引用。这是否可能取决于您是否可以摆脱 Initialise 功能并适应 RAII(请这样做!)。之后,这是否真的是个好主意取决于您的用例;如今,您几乎可以肯定地通过使用其中一种智能指针类型来使所有权和生命周期语义更加清晰——即使它只是一个std::weak_ptr

无论如何,说得更笼统。

指针“总是”不好吗?不,当然不是。我几乎想说自己管理动态内存“总是”不好,但我不会一概而论。

你应该避免他们吗?是的。

不同之处在于后者是引导您远离手动内存管理的指南,而前者是一种尝试性的禁止。

【讨论】:

@Engineer999:您可以使用众多标准容器之一。如果您尽可能使用那些代码,您的代码将更易于维护且没有错误,而不是自行使用免费商店。 这就是您引用的建议的目的。 如果您想从“指针”中排除标准智能指针,总有std::make_unique,shared 至少可以为您处理指针生命周期。是否有充分的理由使用原始指针?是的!为工作使用正确的工具。只是原始指针很少是适合这项工作的工具。 加一只来自食肉猫【参考方案2】:

不,在 C++ 中使用指针一点也不差,我一遍又一遍地看到这种反建议。不好的是自己管理指针,除非你正在创建一个指针管理的低级实体。

再次,我将做出非常明确的区分。使用指针很好。很少有真正的 C++ 程序可以不用 USING 指针。管理指针是不好的,除非你正在使用指针管理器。

【讨论】:

“我一遍又一遍地看到这个反建议”——真的吗?我不。它几乎完全是关于拥有指针,然后是关于在适当的情况下使用引用。但是一旦考虑到这一点,原始指针的剩余用途就很少了(尽管 OP 可能就是其中之一)。 @KonradRudolph,这始终是一种确认偏差 :) 你必须在这里相信我,我个人到处都看到过,包括我以前的一个团队的编码指南。 @KonradRudolph,即使您自己的答案也有同样的问题:“现代 C++ 习语通常根本不需要指针”——来自您的链接。跟我谈谈不使用 const char*。 我自己的回答与我上面的评论非常一致:一旦你消除了原始指针的遗留用途,剩下的用途就不多了。然而,剩下的当然是完全有效的(不过,需要注意的是,如果 C++ 对这种类型的指针没有特权语法,而是使用类模板 raw_ptraddress 来表示它们,我会更喜欢它)。我不同意我的回答有这样的问题。我不确定您所说的“不使用const char*”是什么意思。 @SergeyA 顺便说一句,我认为很明显我们都同意这件事。我们只是在措辞上不同意(我相信从根本上说)。这是我的观点:这个问题再次表明 C++ 中的指针通常被教得多么糟糕,导致代码糟糕、有缺陷、效率低下。打破这个循环的方法是从 C++ 中的指针中获取特权位置。指针应该被认为只是另一种类型,可以酌情考虑使用,而不是作为默认的首选类型。作为一名程序员,我在需要的地方使用原始指针。作为老师,我痛恨 C++ 原始指针语法【参考方案3】:

指针可以是nullptr,而引用必须始终绑定到某个东西(并且不能随后重新绑定到其他东西)。

这是您选择设计的主要区别和主要考虑因素。

指针的内存管理可酌情委托给std::shared_ptrstd::unique_ptr

【讨论】:

同样重要的是,引用一旦绑定就不能反弹。指针可以。 但是如果只使用引用,那么我不能使用“new”和“delete”在免费商店中创建对象? @Engineer999 反正你也不想这么做。 @KonradRudolph:这是一个很好的观点。如果你不介意,我已经抄袭了。 虽然在技术上是准确的,但我认为这个答案并没有看清问题的实质。【参考方案4】:

好吧,我从来不需要 2 个类来进行互惠引用,而且出于充分的理由,你怎么知道如何测试这些类?如果稍后您需要更改 2 个类的通信方式,您可能必须更改两个类中的代码)。 您可以通过多种方式解决问题

    实际上您可能只需要 1 个班级(您已经分成了很多班级) 您可以为一个类注册 Observer(使用第 3 个类,在这种情况下,您最终会得到一个指针,但至少 2 个类的耦合度较低,并且更容易测试它们)。 您可以考虑(也许)一个新接口,只需要一个类来调用另一个类上的方法 您可以将 lambda(或者如果您没有 C++11,则为仿函数)传递给该类的方法之一,从而无需反向引用 您可以在方法中传递类的引用。 也许您只需要很少的类,而实际上您需要第三类而不是与两个类进行通信。 您可能需要一个访客(也许您确实需要多次调度)

上面的一些解决方法需要指针,有些不需要。由你选择;)

注意:但是你所做的对我来说完全没问题(我看到你只在构造函数中做了一些诡计,但可能你有更多省略的代码,在这种情况下会给你带来麻烦)。在我的情况下,我将一个类“注册”到另一个类中,然后在调用构造函数之后,我只有一个类调用另一个类,反之亦然。

【讨论】:

【参考方案5】:

首先,当您的设计中存在循环依赖时,请三思而后行,并确保它是可行的方法。尝试使用Dependency inversion principle 来分析和修复您的依赖关系。

有人告诉我要避免在 C++ 中使用指针。但是在我尝试编写的代码中,我似乎无法避免它们,或者我可能错过了其他出色的 C++ 功能。

指针是一种强大的编程工具。与 C++(或任何一般编程语言)中的任何其他特性一样,当它们是正确的工具时,它们必须被使用。在 C++ 中,您还可以访问与使用中的指针类似但语法更好的引用。此外,它们不能为空。因此它们总是引用一个有效的对象。

因此,当您需要时使用指针,但尽量避免使用 原始 指针,并尽可能选择智能指针作为替代。这将保护您免受一些微不足道的内存泄漏问题,但您仍然必须注意您的对象生命周期,并且对于每个动态分配的对象,您应该清楚地知道谁创建它以及何时/谁将释放为对象分配的内存。

指针(和引用)通常非常有用,因为它们可用于将参数传递给方法通过引用,这样您就可以避免在堆栈中按值传递重对象。例如,假设您有一个非常大的重对象数组(其中 copy/= 运算符很耗时)并且您想对这些对象进行排序。一种简单的方法是使用指向这些对象的指针,因此您只需移动非常轻量级数据类型的指针(基本上是机器地址的大小),而不是在排序操作期间移动整个对象。

【讨论】:

以上是关于在 C++ 中使用指针总是不好的吗?的主要内容,如果未能解决你的问题,请参考以下文章

在一个类变量中收集所有实例总是不好的吗?

C++中的指针是按值传递的吗?

[ZZ]C++中,引用和指针的区别

C++ 使用指向相同函数的指针作为模板参数是不是总是会导致相同的实例化?

C++ 结构体与类指针

在 C++ 中使用指向数组中对象的指针会更好吗?