9

I was not expecting this code to compile:

#include <iostream>
#include <memory>

class A
{
public:

    inline int get() const
    {
        return m_i;
    }

    inline void set(const int & i)
    {
        m_i = i;
    }

private:

    int m_i;
};

int main()
{
    const auto ptr = std::make_unique< A >();

    ptr->set( 666 ); // I do not like this line    D:<
    std::cout << ptr->get( ) << std::endl;

    return 0;
}

If ptr was a raw C pointer, I would be ok with that. But since I'm using a smart pointer, I can't understand what is the rationale behind this.

I use the unique pointer to express ownership, in Object Oriented Programming this could be seen as an object composition ("part-of" relationship).

For example:

class Car
{
    /** Engine built through some creational OO Pattern, 
        therefore it has to be a pointer-accessed heap allocated object **/
    std::unique_ptr< Engine > m_engine;
};

Or:

class A
{
    class Impl;

    std::unique_ptr< A::Impl > m_impl; // PIMPL idiom
};

If an instance of a class Car is constant, why the Engine should not be constant as well? If it was a shared pointer I would have been completely fine with that.

Is there a smart pointer who can reflect the behaviour I want?

nyarlathotep108
  • 5,275
  • 2
  • 26
  • 64

2 Answers2

20

It's pretty simple:

const auto ptr = std::make_unique< A >();

This means that the pointer itself is constant! But the object it holds is not. You can see it working the other way around...

A *const ptr = new A();

It's the same. The pointer is constant (can't be modified to point elsewhere), but the object is not.

Now, you probably meant that you want something like this, no?

const auto ptr = std::make_unique<const A>();

This will create a constant pointer to a constant A.

There's also this other way...

auto ptr = std::make_unique<const A>();

The object is constant, but not the pointer.

BTW: That "const-propagation" you talk about applies to C++, too, in the same way you stated it.

3442
  • 8,248
  • 2
  • 19
  • 41
  • Yes that was clear, sorry for asking too many questions inside only one question. One of those was indeed: why a unique smart pointer has been made by the C++ committee in the way it acts just like a C raw pointer? What is the rationale behind this? – nyarlathotep108 Nov 06 '15 at 11:06
  • 1
    @nyarlathotep108: At the start, it appears to be a non-sense, until you realize that a pointer is just another object that happens to hold the address of yet another object. There are times when you want to change the pointer to point elsewhere, but it may point to constant objects. In this cases (they're pretty common, though), this pattern will save your job. – 3442 Nov 06 '15 at 11:09
  • I do not find it useful right now, but I see your point is probably a valid one. Maybe when I want they to act the same way as references act (from a const correctness point of view), I will write my own unique pointer, I can't see any other way. – nyarlathotep108 Nov 06 '15 at 18:35
  • I don't see any reason against `auto ptr = std::make_unique();`. The OP intent is to not modify the object through the pointer, but he may well want to reseat it. – M.M Mar 14 '16 at 04:09
  • @M.M: You're right. I can't remember what I was thinking about when writing that, so removed that note. – 3442 Mar 14 '16 at 06:36
0

Though it is late answer, there is a solution to propagate constness in the class.

std::experimental::propagate_const :

std::experimental::propagate_const is a const-propagating wrapper for pointers and pointer-like objects. It treats the wrapped pointer as a pointer to const when accessed through a const access path, hence the name.

#include <iostream>
#include <memory>
#include <experimental/propagate_const>

class Car
{
    
    public:
        void start();
        void start() const;
    
    private:
        struct Engine;
        std::experimental::propagate_const<std::unique_ptr<Engine>> m_engine;
};


struct Car::Engine{
    void start(){
        std::cout<< "Engine started (non const)"<<std::endl;
    }
    
    void start() const{
        std::cout<<"Engine started (const)" <<std::endl;
    }
};

void Car::start(){
    m_engine->start();
    std::cout<< "Car started (non const)"<<std::endl;
}

void Car::start() const{
    m_engine->start();
    std::cout<< "Car started (const)"<<std::endl;
}


int main()
{
    Car c1;
    c1.start();
    
    const Car c2;
    c2.start();
    
    return 0;
}

o/p:

Engine started (non const)
Car started (non const)
Engine started (const)
Car started (const)

DEMO

TruthSeeker
  • 1,539
  • 11
  • 24