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

时间:2016-02-19 16:57:15

标签: c++ oop pointers

我对C ++很新,我试图理解构建类的良好实践。

我们说我有一个班级Foo

class Foo {
  public:
    double foo;
    Foo(double foo);
    Foo add(Foo f);
}

我想创建一个类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);
}
  

因为我没有为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));
}
  

注意:我必须将foo1foo2声明为const才能使构造函数正常工作。   它仍然失败,因为foo3我正在取一个临时结果的地址,这是非法的。

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

任何帮助表示感谢。

3 个答案:

答案 0 :(得分:6)

使用指针作为成员很好,但在你的情况下,你只是在处理一个小小的打嗝,它确实不能保证使用指针,使用指针可能是危险的,如问题我很快就会指出。

  

原样,它不起作用,因为我没有为Foo定义默认构造函数。

使用Bar的初始值设定项可以轻松解决这个问题:

Bar(const Foo &f1, const Foo &f2) : foo1(f1), foo2(f2), foo3(f1.add(f2)) {}

如下所示:

#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;
}

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

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

foo3 = &(f1.add(f2));

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

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

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

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

Bar(Foo* f1, Foo* f2);

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

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

~Bar()
{
    delete f3;
}

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

将指针用于您绝对不想复制的大型对象,以及不能使用移动操作的地方。

---编辑---

传递指针所有权的问题已经在现代C ++(C ++ 11及更高版本),智能指针&#34;,特别是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;
}

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

优雅的是,当Bar超出范围时,unique_ptr将确保他们拥有的对象被我们破坏 - 我们不必记住{{1}他们。

在上面的例子中,将delete作为成员而不是指针可能要好得多。

答案 1 :(得分:4)

如果对象不太昂贵而无法传递,我建议使用对象作为成员。

如果由于某种原因需要使用指针,则需要制定所有权政策。 Bar对象是否拥有对象? Bar只保存指向对象的指针,但不负责释放它们使用的资源吗?

如果Bar拥有Foo个对象,则更喜欢使用其中一个智能指针。您需要使用new制作这些对象的副本并保留这些指针。

以下是我的看法:

class Bar {
  public:
    std::unique_ptr<Foo> foo1;
    std::unique_ptr<Foo> foo2;
    std::unique_ptr<Foo> foo3;
    Bar(const Foo &f1, const Foo &f2) : foo1(new Foo(f1)), ... {}
};

std::unique_ptr没有复制构造函数。因此,您必须为Bar提供复制构造函数,并从副本中正确初始化其成员。

如果Bar不拥有Foo个对象,您可以通过使用引用作为成员数据来获取。

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

答案 2 :(得分:0)

我认为对象与原始变量相同是无稽之谈。

class Foo {
  public:
    double _stocks;
    Business* _business;
    Foo(double stocks, Business* business):_stocks(stocks), _business(business){}
    Foo* add(const Foo& f) {
        _stocks += f._stocks;
        _busines->merge(f._business);
        return this;
    }
    virtual ~Foo() {  delete _business;  }
}
class Bar {
  public:
    Foo* _foo1;
    Foo* _foosub;
//    Foo* _foo3;
    Bar(Foo* f1, Foo* f2); // unable const for f1 at least 
}
Bar::Bar(Foo* f1, Foo* f2):
{
    _foo1 = f1;
    _foosub = f2;
    _foo1.add(*f2);
    // _foo3 is the same as _foo1
}
void main() {
    Foo company1(100.00, BusinessFactory.create("car"));
    Foo company2(2000.00, BusinessFactory.create("food"));
    Bar conglomerate(&company1, &company2);
    // to be continued
}