c++ 指针参数:创建指向值的副本是不是有意义,然后再次将副本存储为指针?

Posted

技术标签:

【中文标题】c++ 指针参数:创建指向值的副本是不是有意义,然后再次将副本存储为指针?【英文标题】:c++ pointer arguments: does it make sense to create a copy of the value pointed too, then store the copy as pointer again?c++ 指针参数:创建指向值的副本是否有意义,然后再次将副本存储为指针? 【发布时间】:2013-10-06 03:56:55 【问题描述】:

我正在将一个项目迁移到 c++,因为我在用 c# 开发它时遇到了性能上限。然而,这是我第一次使用 c++,我发现自己做了很多看起来不太正确的事情......

考虑以下抽象示例:

class ClassC

    ClassC::ClassC(int option)
    
        //do something
    


class ClassB

    ClassC* objC

    ClassB::ClassB(ClassC* objC)
    
        this->objC = new ClassC(*objC);
    


class ClassA

    void functionA(void)
    
        ClassB objB (&ClassC(2));
    

ClassA 有一个创建 ClassB 的函数。 ClassB 的构造函数接受一个 ClassC,objC。 objC 通过引用传递,因为 ClassC 不是原始类型,而是通过引用存储,因为 ClassC 没有默认构造函数。但是,由于objC是在静态内存中创建的,并且会在functionA完成时被销毁,所以ClassB需要将objC指向的值复制到动态内存中,然后存储一个指向该副本的指针。

这对我来说似乎很绕,让我觉得我在错误地接近某些东西。这是 c++ 中的标准操作吗?

编辑:每个人似乎都在说 ClassB objB (&ClassC(2)); 行不正确,因为 ClassC 对象的值将在 ClassB 复制它之前丢失。但是我已经编译了我的示例,但事实并非如此。这是修改后的工作代码:

class ClassC

    int option;

public:
    ClassC::ClassC(int option)
    
        this->option = option;
    

    int ClassC::getOption(void)
    
        return option;
    
;

class ClassB

    ClassC* objC;

public:
    ClassB::ClassB(ClassC* objC)
    
        this->objC = new ClassC(*objC);
    

    int ClassB::getOption(void)
    
        return objC->getOption();
    
;

class ClassA

public:
    static ClassB functionA(void)
    
        return ClassB (&ClassC(2));
    
;

int main(void)

    ClassB objB = ClassA::functionA();

    int test = objB.getOption(); //test = 2, therefore objC was copied successfully.

    return 0;

【问题讨论】:

您是否遇到任何错误?它们是什么? ClassB objB (&ClassC(2));这里取的是临时地址。这不是一件好事。 您需要了解对象生命周期和所有权语义。在c#,这些事情你不必担心。 1.如果可以避免,请不要使用指针。 2. 传递 const ref 而不是指针 除了模糊的建议之外,很难建议如何处理示例代码,因为您的意图并不十分清楚。 【参考方案1】:

不确定您的真正问题是什么,但您的代码似乎很脆弱。我想重写你的代码,就像你展示的那样:

class ClassC

    explicit ClassC(int option)
//  ^^^^^^^^ stop implicit conversion, if constructor takes one parameter
    
        //do something
    
;

class ClassB

    ClassC objC;                         // store by value instead of pointer. 
                                         // Even smart pointer will be better option than raw pointer

    explicit ClassB(const ClassC& objC)  // pass by const reference instead
    : objC(objC)                         // use member initializer list to initialize members
    
    
;

class ClassA

    void functionA(void)
    
        ClassB objB(ClassC(2));
    
;

【讨论】:

在这段代码中,您正在创建一个临时 ClassC,然后将引用传递给一个临时对象,然后将其再次复制到 objC 中。我将构造函数更改为explicit ClassB(ClassC c) : objC(std::move(c)) ,这将导致没有副本,因为临时将被移动到classB构造函数参数中(因为它是一个右值),然后将用于移动构造objC。 @MuhammadFaizan 我明白你的意思。但是,将移动语义添加到 OP atm 并没有太大价值。 @MuhammadFaizan 临时文件仍将在范围内,并且他没有存储引用,因此以这种方式使用它没有任何问题。此外,ClassB 将调用ClassC 的复制构造函数,再次避免引用临时问题。移动语义只会有助于提高性能(避免制作所有这些副本)。 鉴于我认为 OP 正在寻找什么,我也对此表示赞同(并认为它应该是公认的答案)。我同意移动语义也是理想的方式。我发布的答案是为了涵盖 OP 正在寻找共享语义的可能性,但我认为这可能更符合他们的目标。 作为旁注,这个答案与 OP 发布的内容基本相同(只是没有动态内存):传递一个临时的引用,这没关系,因为它正在被复制(在 OP ,通过动态内存分配,这里是静态内存)。【参考方案2】:

获取临时地址并将其保存以备后用是一个很大的禁忌。

ClassB objB (&ClassC(2));  // taking address of temporary

此外,即使在函数参数列表中传递临时的 const-reference 也不会比函数调用进一步延长生命周期。 IE。一旦构造函数完成触发,引用就是 toast,所以:

class ClassB

    const ClassC& objC;

public:
    ClassB(const ClassC& objC) : objC(objC)
    
    
;

也不行。 More info can be read here about the details for why。

如果你这样做,它工作:

ClassC objC;
ClassB objB(objC);

但话又说回来,您的原始样本也是如此。

保证外部对象的生命周期的一种方法是通过智能指针所有权动态分配对象。考虑一下:

class ClassB

    std::shared_ptr<ClassC> ptrC;

public:

    ClassB(std::shared_ptr<ClassC> ptrC)
        : ptrC(ptrC)
    
        // access the instance with ptrC->member()
    
;

现在你可以这样做了:

ClassB objB(std::make_shared<ClassC>(2));

即使objB 是值复制的(例如在容器上的排序操作等),共享实例仍然完好无损。最后一个出门的人关了灯(在这种情况下,删除了共享的ClassC 对象)。

显然,对于仅由单个父级持有的单个实例而言,这是毫无意义的。在这种情况下,我完全同意其他强烈建议您使用移动语义的答案。但是,如果您确实需要共享资源,这是考虑这样做的一种方法。


EDIT将传递构造函数添加到 ClassB 作为一个简单的示例。

我刚刚意识到每个人都非常热衷于帮助您构建 ClassC 对象,以至于您可能只需要一种向objC 提供参数以进行构建的方法。 IE。也许您完全打算在 objB 上完全拥有自己的 objC 私有实例,而您所需要的只是一种获取参数以进行初始化的方法。

这就是构造构造器初始化列表的用途。请参阅下面的代码,它(基于您的 cmets,可能会为您工作,并且非常更易于理解。

class ClassB

    ClassC objC;

public:
    // default constructor. initializes objC with default value
    ClassB() : objC(0)
    
    

    // explicit pass-through of params to `objC` construction
    explicit ClassB(int option) : objC(option)
    
    
;

这使您在ClassA 中的代码变得简单:

ClassB objB(2);

这将调用ClassB::ClassB(int),将提供的参数传递给ClassC类型的内部objC对象实例的构造。

【讨论】:

随机观察:我只是第一次学习 C++,令人惊讶的是,在 C# 或 Java 中几乎不需要思考的问题的最佳解决方案是移动语义或共享指针,两者都是这是 C++11 的概念。我想知道每个人在新标准之前是如何生存的:) @MuhammadFaizan 仔细编码。移动语义主要提供了一种优化(之前它会涉及到对象的复制)。不过,智能指针已经存在了很长一段时间。在使用这些模板之前,需要进行大量调试并确保正确管理内存。 @MuhammadFaizan 我来自 C 和 C++,因为 Bjarne 首次发明了 C++,毫无疑问,C++11 为我们解决的许多问题提供了长时间在 C++ 的过去有经验。例如,完美转发严重改变了世界。 @WhozCraig“一旦构造函数完成触发引用就是吐司”查看我的编辑......我已经尝试编译它,但事实并非如此。我目前这样做的方式确实产生了预期的结果,尽管它可能在语义上不正确。 @WhozCraig “最后一个出门的人关了灯”我不得不承认我不完全理解智能指针(我相信你可以说出来),但从你的描述来看,似乎这不一定是我要找的。在我的实际程序中,删除objC确实应该是ClassB的责任:这就是为什么ClassA在静态内存中创建它,然后ClassB将它复制到动态内存并负责删除它。对此有什么想法吗?【参考方案3】:

ClassC 的构造函数无关紧要,因为将调用的是复制构造函数

class ClassC

    ClassC(int option) // defines a constructor that takes an int
    
        //do something
    


class ClassB

    ClassC* objC

    ClassB(ClassC* objC)
    
        this->objC = new ClassC(*objC); // dereferences objC calling ClassC::ClassC(const Class& obj) - the default copy constructor.
    


class ClassA

    void functionA(void)
    
        ClassB objB (&ClassC(2)); // passing a reference to a temporary ... bad idea, but since it is copied in ClassB (the object, not the pointer), it will appear okay - if your compiler lets this compile (newer ones should/will likely throw an error "cannot take address of rvalue temporary")
    

总而言之,如果上面提到了许多建议,这段代码会更好,但值得注意的是,ClassC 的复制构造函数是在 ClassB 中调用的。

【讨论】:

【参考方案4】:
ClassB objB (&ClassC(2));

这是一件坏事。您正在获取一个临时对象的地址,该地址将在此行之后消失。如前所述,尝试使用引用而不是指针。大多数时候,您可以用引用替换指针,这是一种非常安全的方法(它永远不会是“NULL”)。

你可以写

class ClassB

    ClassC& objC;

    ClassB::ClassB(const ClassC& objC) :
        objC(objC)
    
    

【讨论】:

我们如何确保 objC 的生命周期比 ClassB 实例更长? 我们如何从 const-reference 中初始化一个引用? 我不同意这个答案。我认为在大多数情况下,存储引用与存储指针一样糟糕(甚至可能更糟)。此外,就像@WhozCraig 指出的那样,这段代码无法编译。 是的,很抱歉。我想让成员 objC 不是引用,而是 ClassB 的简单成员,因为在我看来他想复制参数 objC。

以上是关于c++ 指针参数:创建指向值的副本是不是有意义,然后再次将副本存储为指针?的主要内容,如果未能解决你的问题,请参考以下文章

C++ - 指针总结

C++指针的算术运算 关系运算

c++“cv::Mat::row”: 函数调用缺少参数列表;请使用“&cv::Mat::row”创建指向成员的指针?

在 C++ 中使用指针和指向指针参数的指针创建构造函数

指向 std::thread 的共享指针是一种不好的做法吗?

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