将指针用作类成员是一种好习惯吗?

Eri*_*uth 3 c++ oop pointers

我是C++的新手,我正在努力理解构建类的良好实践.

假设我有一节课Foo:

class Foo {
  public:
    double foo;
    Foo(double foo);
    Foo add(Foo f);
}
Run Code Online (Sandbox Code Playgroud)

我想创建一个Bar由两个Foo对象组成的类,并在构造时创建第三个对象.

第一个选项:作为类成员的对象

class Bar {
  public:
    Foo foo1;
    Foo foo2;
    Foo foo3;
    Bar(const Foo &f1, const Foo &f2);  
}

Bar::Bar(const Foo &f1, const Foo &f2):
{
  foo1 = f1;
  foo2 = f2;
  foo3 = f1.add(f2);
}
Run Code Online (Sandbox Code Playgroud)

因为我没有为其定义默认构造函数,所以它不起作用Foo.

第二种选择:指针作为班级成员

class Bar {
  public:
    const Foo* foo1;
    const Foo* foo2;
    const Foo* foo3;
    Bar(const Foo &f1, const Foo &f2);  
}

Bar::Bar(const Foo &f1, const Foo &f2):
{
  foo1 = &f1;
  foo2 = &f2;
  foo3 = &(f1.add(f2));
}
Run Code Online (Sandbox Code Playgroud)

注:我必须声明foo1,并foo2作为const该构造的工作.它仍然失败,因为foo3我正在采取临时结果的地址,这是非法的.


哪个选项更自然(我如何修复错误)?我觉得第一个选项可能更好,但是我的Foo对象必须在内存中创建两次,不是吗?(一次调用构造函数,第二次调用构造函数本身)

任何帮助赞赏.

kfs*_*one 6

使用指针作为成员是好的,但在你的情况下,你只是在一个小的打嗝工作,实际上不保证指针的使用,并且使用指针可能是危险的,我将很快指出一个问题.

因为我没有为Foo定义默认构造函数,所以它不起作用.

使用Bar的初始化程序可以轻松解决这个问题:

Bar(const Foo &f1, const Foo &f2) : foo1(f1), foo2(f2), foo3(f1.add(f2)) {}
Run Code Online (Sandbox Code Playgroud)

如下所示:

#include <iostream>

class Foo {
  public:
    double m_foo;
    Foo(double foo) : m_foo(foo) {}
    Foo add(Foo f) { f.m_foo += m_foo; return f; } // returns temporary!
};

class Bar {
  public:
    Foo m_foo1;
    Foo m_foo2;
    Foo m_foo3;
    Bar(const Foo &foo1, const Foo &foo2);  
};

Bar::Bar(const Foo &foo1, const Foo &foo2)
    : m_foo1(foo1)
    , m_foo2(foo2)
    , m_foo3(m_foo1.add(m_foo2))
{
}

int main() {
    Foo foo1(20.0);
    Foo foo2(22.0);
    Bar bar(foo1, foo2);

    std::cout << bar.m_foo3.m_foo << "\n";

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

现场演示:http://ideone.com/iaNzJv

在你的指针解决方案中,你引入了一个明显的指针问题:一个指向临时的指针.

foo3 = &(f1.add(f2));
Run Code Online (Sandbox Code Playgroud)

f1.add返回一个临时Foo,你获取地址,然后它就消失了.这是一个悬垂的指针.

您的指针实现也没有显式地将指针作为其输入,因此f1和f2可能会遇到同样的问题:

Bar(Foo(20), Foo(22));  // two temporary Foos passed by reference
                        // but having their addresses taken. ouch.
Run Code Online (Sandbox Code Playgroud)

如果你正在指点,最好在你班上的api那样做; 你将不得不关心指向的事物的生命周期,并试图让调用者更容易告诉你这样做.

Bar(Foo* f1, Foo* f2);
Run Code Online (Sandbox Code Playgroud)

但是现在如果你要拥有F3,你将负责管理它的内存:

Bar(Foo* f1, Foo* f2)
    : foo1(f1), foo2(f3), foo3(new Foo(*f1.add(*f2)))
{}

~Bar()
{
    delete f3;
}
Run Code Online (Sandbox Code Playgroud)

因此,在您的示例中,使用成员可能会更好.

保存对您绝对不想复制的大对象的指针的使用,以及不能使用移动操作的地方.

---编辑---

输送指针的所有权的问题已经很大程度上解决了在现代C++(C++ 11或更高版本),通过"智能指针",尤其是std::unique_ptrstd::shared_ptr.

尽管它需要学习一些较新的C++概念,但它通常被认为是使用这些而不是原始指针的最佳实践.

#include <memory>

struct Foo {};
class Bar {
public:
    std::unique_ptr<Foo> m_f1; // we will own this
    std::unique_ptr<Foo> m_f2; // and this

    Bar(std::unique_ptr<Foo> f1) // caller must pass ownership
        : m_f1(std::move(f1))    // assume ownership
        , m_f2(std::make_unique<Foo>()) // create a new object
    {}

    ~Bar()
    {
        // nothing to do here
    }
};

int main() {
    auto f = std::make_unique<Foo>();
    Bar(std::move(f)); // the 'move' emphasizes that
                       // we're giving you ownership
    // 'f' is now invalid.

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

现场演示:http://ideone.com/9BtGkn

这样做的优雅之处在于,当Bar超出范围时,unique_ptrs将确保他们拥有的对象被我们破坏 - 我们不必记住delete它们.

在上面的例子中,创建m_f2成员而不是指针可能要好得多.