我应该避免在现代 C++ 中引用指针还是在这种特殊情况下可以

Posted

技术标签:

【中文标题】我应该避免在现代 C++ 中引用指针还是在这种特殊情况下可以【英文标题】:Should I avoid reference to pointer in modern C++ or it is okay in this special case 【发布时间】:2019-02-03 19:16:50 【问题描述】:

我知道,除非真的需要,否则必须避免使用现代 C++ 中的原始指针(或更准确地说是 newdelete)。我相信在图形实现方面,指针非常有用。不过,我很高兴听到 cmets 对此的看法。

我在玩二叉搜索树并编写insert 函数。我使用两种不同但相似的方法来实现,一种使用指针,另一种使用指针引用。显然我想更改指针本身(参考 ptr)。想知道它们中的任何一个是否有优点或者不应该使用并且必须使用 const 引用或智能指针(现代方式)编写。

Node *insert_ptr(Node *root, int data)

    if (root == NULL)
        return create(data);
    else if (data < root->data)
        root->left = insert(root->left, data);
    else
        root->right = insert(root->right, data);
    return root;


void insert_ref_to_ptr(Node *&root, int data)

    if (root == NULL)
        root = create(data);
    else if (data < root->data)
        insert(root->left, data);
    else
        insert(root->right, data);

如果您想试一试,我将在下面提供其余代码。您可能想使用1 3 6 8 10 14 13 4 7 作为输入

struct Node

    int data;
    Node *left;
    Node *right;
;

void display(Node *root)

    if (root != NULL)
    
        display(root->left);
        cout << root->data << " ";
        display(root->right);
    


Node *create(int data)

    Node *node = new Node();
    node->data = data;
    node->left = node->right = NULL;
    return node;


int main()

    Node *root = NULL;
    vector<int> nodes;

    string line;
    int value;
    getline(cin, line);
    stringstream split(line);
    while (split >> value)
        nodes.push_back(value);
    /*
    root = insert_ptr(root, nodes[0]);
    for (int i = 1; i < nodes.size(); i++)
        insert_ptr(root, nodes[i]);
    */

    for (int i = 0; i < nodes.size(); i++)
        insert_ref_to_ptr(root, nodes[i]);
    display(root);
    return 0;


【问题讨论】:

绝对不是一个好的设计。所有这些函数都应该在类中,左右应该是唯一的指针。 你绝对应该避免使用原始指针,除非你 100% 确定你会需要它们。 @MatthieuBrucher 绝对正确。但我不关心这里的设计。 那么这个问题有什么意义呢? 您问的是二叉树、封装还是良好的 C++ 实践?如果你想new/delete,你必须完全封装它。不过,您没有理由在这里进行新建/删除。 【参考方案1】:

that pointers in modern C++ must be avoided unless really needed 错了。

应避免使用newdelete,而应使用唯一和共享指针。

但如果您不转移所有权,您仍将通过引用或原始指针传递对象。

您的Node 具有所有权关系,因此应该是:

struct Node

    int data;
    std::unique_ptr<Node> left;
    std::unique_ptr<Node> right;
;

struct Node

    int data;
    std::shared_ptr<Node> left;
    std::shared_ptr<Node> right;
;

您的display 函数不需要所有权,因此您将节点作为指针(或引用)传递

void display(Node *root)

    if (root != nullptr)
    
        display(root->left);
        cout << root->data << " ";
        display(root->right);
    

由于您不打算更改它,所以我会使用 const ref:

void display(const Node &root)

    if(root.left != nullptr) 
        display(*root.left);
    

    cout << root.data << " ";

    if(root.right != nullptr) 
        display(*root.right);
    

insert_ref_to_ptr 是一个非常糟糕的构造,因为不清楚它是否转移了任何所有权,也不清楚它调用create(data) 在其中使用new 创建一个节点。

创建看起来像这样:

std::unique_ptr<Node> create(int data)

    auto node = std::make_unique<Node>();
    node->data = data;
    return std::move(node);

insert_ref_to_ptr 函数类似:

void insert_ref_to_ptr(std::unique_ptr<Node> &root, int data)

    if (root == nullptr)
        root = std::move(create(data));
    else if (data < root->data)
        insert(root->left, data);
    else
        insert(root->right, data);

【讨论】:

你自己的insert函数会是什么样子? 非常有兴趣了解您将如何实现整个事情。如果可以的话,这将是非常有教育意义和帮助的。 @KarimSole 我将如何实现该树,取决于用例。但是您的问题更多的是关于是否仍应使用指针,以及对指针的引用和引用。是的,应该,但你用它做什么总是很重要的。我建议你看一下 “Leak-Freedom in C++... By Default.” of Herb Sutter 的谈话(或他或 Titus Winter 的任何其他谈话) 好的,先生。对于我的具体案例,我很想看看有经验的开发人员如何适合这个简单的用例。请您在最后一部分努力。高度赞赏。 是我理解有误,还是display在这里引用有误?树结构的通常模式是叶节点将leftright 作为nullptr,在这种情况下,display 中必要的取消引用将调用 UB。 (root != NULL 检查也没有多大意义。)【参考方案2】:

我知道在现代 C++ 中必须避免使用指针,除非真的需要。

那不是真的。您可能听说过,如果您的要求符合标准化模式/解决方案,例如unique_ptr,您应该避免使用原始(裸)指针。

显然我想更改指针本身(参考 ptr)。想知道它们中的任何一个是否有优点或者不应该使用并且必须使用 const 引用(现代方式)编写。

如果你想有输出参数,你有很多方法可以做到:

返回值。 对值的引用(作为参数)。 指向值的指针(作为参数)。 structstd::tuple 中的多个返回值或使用结构化绑定。 成员变量(仅适用于类中的成员函数)。 全局变量。

没有一种适用于所有情况的最佳方法。您使用哪一个取决于您以及您将如何使用该功能。在某些情况下,它还取决于项目的编码风格指南(例如,在引用与指针的情况下)。例如:

如果只有一个输出参数,有些人更喜欢使用返回值。 一些库更喜欢将返回值专门用于错误代码。 一些编码风格指南避免引用,因为他们希望在调用者中明确看到您正在传递地址(例如 f(a)f(&amp;a))。 在成员函数中,您通常使用成员变量。 在几乎所有情况下,您都应该避免使用全局变量。

必须使用 const 引用(现代方式)编写。

顺便提一下,你写的不是const引用,只是Note *的引用。而且它不是“现代 C++”:引用是第一个 C++ 标准的一部分。

【讨论】:

基本上没有偏好或最佳实践?! 对于一般情况,不,不是。 确实很有帮助。如果您有机会使用自己的风格编写insert 函数,我会非常激动。很好奇专家会怎么写这样的案例。 @KarimSole 在 C 中,您的第一个界面可能是最流行的解决方案(返回指针)。在 C++ 中,几乎每个人都会设计一个类来封装数据结构(参见标准 C++ 库中的示例)——因此,insert 将是一个成员函数。

以上是关于我应该避免在现代 C++ 中引用指针还是在这种特殊情况下可以的主要内容,如果未能解决你的问题,请参考以下文章

[Js-c++]c++中的指针引用和数组名

一文掌握 C++ 智能指针的使用

在 Node.JS 中应该避免循环还是有特殊的方法来处理它们?

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

C++:取消引用指针

如何在 C++ 中将指针转换/转换为引用