user7431005
user7431005

Reputation: 4539

Should the member of class be a pointer?

I'm a bit confused. If you search about the usefulness of (smart) pointers you get very different opinions. Almost everybody agrees that one should use smart pointers over normal pointers, but there are also many opinions that you should not use pointers at all in modern C++.

Lets consider this abstract situation (Which is similar to my problem): You have one class "Clothes" which has a member of type "Hat"

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
}

Is there any Difference in run time if I would change Hat to Hat* (or a unique_ptr<Hat>?). (In many functions of Clothes you would need to call something from Hat)

The reason I ask is because there are many different types of hats. Like sombreros, sun hats, helmets, beanies and cowboy hats. Right now my class hat has an enumerator which stores the hat type. The hat type is only relevant in one specific function of Hat but this is the most used function.

Right now I use a simple switch case in this specific function and based on the hat type the function is evaluated a bit differently. This worked fine but I think it would be smarter to simply make an own class for every different hat type which inherit from one single main Hat class and override the one function.

To do so I think I would have to change the member currentHat in Clothes to any type of pointer. I researched if this would have any negative effects on my performance (I thought about maybe because the location of my currentHat object and my Clothes object in memory can be wide apart, but I have no Idea if this would happen and if this would have any negative effects with modern compilers).

During my research I often read that one should avoid pointers, this made me thinking... some of the information I found was also very old and I do not know if this is outdated. Is there any better way to do this?

Has anyone any experience with this kind of problem? Would be good to get some feedback before I spend a lot of time changing my whole project...

Side note: I took Clothes and Hats only as an examples. In my real application I have different types and I am creating many million of objects from type Clothes and I have to call functions of Clothes which will call functions of Hat many many million times, that is why I'm concerned with run time.

Edit: Maybe it is also worth noting that I have entirely avoided pointers in my application so far, mostly because I read in some books that one should try to avoid pointers :-)

EDIT2: Thank you for all your answers. Please note that part of my question was not how to do it (although the answers were very useful in that depart) but rather if the performance will suffer from this.

Upvotes: 2

Views: 188

Answers (3)

bolov
bolov

Reputation: 75785

Ok, let's break down the pointer debacle:

Don't use explicit new/delete in C++

There really isn't any exception to this rule. Except if you are writing a library/framework. Or some fancy stuff that requires placement new. But in user code new/delete should be 100% absent.

This brings us to my next rule:

Don't use raw pointers to denote ownership.

This you will find often on the net as the "Don't use raw pointers" advice/rule. But in my view the problem is not with raw pointers, but with raw pointers that own an object.

For ownership use smart pointers (unique_ptr, shared_ptr). If a pointer is not an owner of the object then it's ok if you use raw pointers. For instance in a tree like structure you can have unique_ptr to the children and a raw pointer to the parent.

You can (arguably) also use a pointer to denote an optional value via nullptr.

You need to use pointers or references for dynamic polymorphism

(Well... there are other ways, like type erasure but I won't go there in my post) If you want polymorphism then you can't use a value. If you can use reference then do, but most of the time you can't. Which often means you have to use unique_ptr.

Upvotes: 5

Quentin
Quentin

Reputation: 63144

Here's a breakdown of your three suggested implementations, and some more:

Plain member: Hat currentHat;

  • Clothes uniquely owns the Hat
  • No dynamic allocation
  • No polymorphism

Smart pointer: std::unique_ptr<Hat> currentHat;

  • Clothes still uniquely owns the Hat
  • Dynamic allocation required
  • Polymorphism available, i.e. can hold instances derived from Hat

Raw pointer: Hat *currentHat;

  • Clothes does not own the Hat
  • Unspecified allocation, need to ensure that the Hat outlives the Clothes
  • Polymorphism still available
  • Perfectly fine if these are your requirements. Fight me :)

Unlisted contenders

Smart pointer: std::shared_ptr<Hat> currentHat;

  • Clothes shares ownership of the Hat with zero or more other Clothes
  • Same as std::unique_ptr otherwise

Reference: Hat &currentHat;

  • Similar to a raw pointer
  • Is non-rebindable, thus makes the object non-assignable -- hence I don't personally like member references in the general case.

Upvotes: 3

Klaus
Klaus

Reputation: 25623

As you need different Heads, you have basically two options:

1) Using a (smart) pointer, this is well known and easy

2) Using std::variant

This is a vary different approach! No longer need of a base class and ( pure ) virtual methods. Instead of this using the std::visit to access the current object in the tagged union. But this comes with a cost: Dispatching in std::visit can be expensive if the compiler did not hardly optimize the call table inside. But if it can, it is simply taking the tag from the variant and use it as a index to the overloaded function from your visit call, here given as a generic lambda. That is not as fast as indirect call via vtable pointer, but not more than a view instructions more ( if it is optimized!).

As always: How you want t go is a matter of taste.

Example:

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();
}

Upvotes: 1

Related Questions