【问题标题】:Should the member of class be a pointer?类的成员应该是指针吗?
【发布时间】:2018-08-25 07:39:15
【问题描述】:

我有点困惑。如果您搜索(智能)指针的有用性,您会得到非常不同的意见。 几乎每个人都同意应该使用智能指针而不是普通指针,但也有许多意见认为在现代 C++ 中根本不应该使用指针。

让我们考虑一下这种抽象情况(这与我的问题相似): 你有一个“衣服”类,它有一个“帽子”类型的成员

class Hat {
public:
    enum class HatType{
        sombrero,
        sun hat,
        helmet,
        beanie,
        cowboy_hat
    };
    // some functions
private:
    HatType type;
    // some members
}

class Clothes {
public:
    // some functions
private:
    Hat currentHat;
    // some other members of other types
}

如果我将Hat 更改为Hat*(或unique_ptr<Hat>?),运行时间是否有任何差异。 (在Clothes 的许多功能中,您需要从Hat 调用某些东西)

我问的原因是因为有许多不同类型的帽子。 像阔边帽、太阳帽、头盔、无檐小便帽和牛仔帽。现在我的班级帽子有一个存储帽子类型的枚举器。 帽子类型仅与Hat 的一个特定功能相关,但这是最常用的功能。

现在我在这个特定的函数中使用了一个简单的switch case,并且根据帽子类型,函数的评估方式略有不同。这工作得很好,但我认为简单地为每个不同的帽子类型创建一个自己的类会更聪明,这些帽子类型继承自一个主要的 Hat 类并覆盖一个函数。

为此,我想我必须将Clothes 中的成员currentHat 更改为任何类型的指针。我研究了这是否会对我的表现产生任何负面影响(我想可能是因为我的currentHat 对象和我的Clothes 对象在内存中的位置可能相距很远,但我不知道这是否会发生,如果这会对现代编译器产生任何负面影响)。

在我的研究过程中,我经常读到应该避免指针,这让我思考......我发现的一些信息也很旧,我不知道这是否已经过时了。有没有更好的方法来做到这一点?

有人遇到过这种问题吗?在我花很多时间改变我的整个项目之前获得一些反馈会很好......

旁注:我仅以ClothesHats 为例。在我的实际应用程序中,我有不同的类型,我从Clothes 类型创建了数百万个对象,我必须调用Clothes 的函数,这将调用Hat 的函数数百万次,这就是为什么我关心运行时间。

编辑:也许还值得注意的是,到目前为止,我在我的应用程序中完全避免使用指针,主要是因为我在一些书中读到应该尽量避免使用指针:-)

EDIT2:感谢您的所有回答。请注意,我的部分问题不是如何做到这一点(尽管答案在那个阶段非常有用),而是性能是否会因此受到影响。

【问题讨论】:

  • 你怎么能不使用指针呢?这可能会杀死多态性
  • 错误的抽象。 A Hat is-a Clothes (呃,这是一个糟糕的类名,因为没有单数)。这应该使用推导来建模。关于指针的问题:它们有其用途。它们可以用来描述有 0 个或 1 个实例。它们可用于对共享所有权进行建模。可能还有许多其他用例。
  • 在你的情况下std::variant 可能会更好。
  • 我不是专业程序员。我用它来做我的科学。因此我之前没有使用过多态性......但我认为开始使用这个方便的工具是个好主意
  • @KillzoneKid:多态性也可以通过引用获得。您不需要 指针来实现多态性。

标签: c++ class oop pointers


【解决方案1】:

好的,让我们分解指针崩溃:

不要在 C++ 中使用显式 new/delete

这条规则确实没有任何例外。除非您正在编写库/框架。或者一些需要放置新的花哨的东西。但在用户代码中new/delete 应该 100% 不存在。

这就引出了我的下一条规则:

不要使用原始指针来表示所有权。

这是您经常在网上找到的“不要使用原始指针”建议/规则。但在我看来,问题不在于原始指针,而在于拥有对象的原始指针。

对于所有权使用智能指针(unique_ptrshared_ptr)。如果指针不是对象的所有者,那么可以使用原始指针。例如,在树状结构中,您可以将 unique_ptr 指向子级,并将原始指针指向父级。

你也可以(可以说)通过nullptr使用一个指针来表示一个可选值。

动态多态需要使用指针或引用

(嗯......还有其他方法,比如类型擦除,但我不会在我的帖子中提到)如果你想要多态,那么你不能使用值。如果你可以使用参考然后做,但大多数时候你不能。这通常意味着您必须使用unique_ptr

【讨论】:

  • 我无法自拔,但如果我看到错字,我必须改正它;)
  • @user463035818 没关系。我真的很感激
  • 我要补充一点,有一个可选的表示...可选值:)
【解决方案2】:

以下是您建议的三个实现的细分,以及更多:

普通会员:Hat currentHat;

  • Clothes 唯一拥有 Hat
  • 无动态分配
  • 无多态性

智能指针:std::unique_ptr<Hat> currentHat;

  • Clothes 仍然唯一拥有 Hat
  • 需要动态分配
  • 可用的多态性,即可以保存从Hat 派生的实例

原始指针:Hat *currentHat;

  • Clothes 不拥有 Hat
  • 未指定分配,需要确保HatClothes 寿命更长
  • 多态性仍然可用
  • 如果这些是您的要求,那就太好了。打我:)

未列出的竞争者

智能指针:std::shared_ptr<Hat> currentHat;

  • Clothes 与零个或多个其他 Clothes 共享 Hat 的所有权
  • std::unique_ptr 相同

参考:Hat &currentHat;

  • 类似于原始指针
  • 不可重新绑定,因此使对象不可分配 - 因此我个人不喜欢一般情况下的成员引用。

【讨论】:

    【解决方案3】:

    当您需要不同的头时,您基本上有两种选择:

    1) 使用(智能)指针,这是众所周知且简单的

    2) 使用 std::variant

    这是一种不同的方法!不再需要基类和(纯)虚拟方法。而不是使用std::visit 来访问标记联合中的当前对象。但这是有代价的:如果编译器几乎没有优化内部的调用表,在 std::visit 中调度可能会很昂贵。但如果可以的话,它只是从变体中获取标签并将其用作访问调用中重载函数的索引,这里以通用 lambda 形式给出。这不如通过 vtable 指针间接调用的速度快,但不会比视图指令多(如果优化了!)。

    和往常一样:你想怎么走是一个品味问题。

    例子:

    class Head1
    {
        public:
            void Print() { std::cout << "Head1" << std::endl; }
    };
    
    class Head2
    {
        public:
            void Print() { std::cout << "Head2" << std::endl; }
    };
    
    class Clothes
    {
        std::variant<Head1,Head2> currentHead;
    
        public:
    
        Clothes()
        {
            currentHead = Head1();
        }
    
        void Do() { std::visit( [](auto& head){ head.Print();}, currentHead); }
    
        void SetHead( int id )
        {
            switch( id )
            {
                case 0:
                    currentHead= Head1();
                    break;
                case 1:
                    currentHead= Head2();
                    break;
    
                default:
                    std::cerr << "Wrong id for SetHead" << std::endl;
            }
        }
    };
    int main()
    {
        Clothes cl;
        cl.Do();
        cl.SetHead(1);
        cl.Do();
        cl.SetHead(0);
        cl.Do();
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-21
      • 1970-01-01
      相关资源
      最近更新 更多