Why does C++ not have a const constructor?

后端 未结 5 586
生来不讨喜
生来不讨喜 2020-11-29 06:23

(Edit: Heavy change because previous example was flawed, which may make some answers/comments seem odd)

This might be an overly contrived, but the following

相关标签:
5条回答
  • 2020-11-29 06:46

    It would not be a const method itself

    If this constructor were not a const method itself, then the internal pointers and such would also not be const. Therefore, it could not set const values into those non-const members.

    The only way to make it work syntactically is for this constructor to require member initialization for all non-mutable members. Essentially, any member not declared mutable would be implicitly declared const when using this constructor. Which is equivalent to making the constructor a const method; only initializers could initialize members. The constructor's body could do nothing with non-mutable members, because those members would be const at that point.

    What you are asking for is syntactically dubious. You're essentially trying to hoodwink the API, storing constant data in an object that is designed for mutable data (which is why you didn't declare the member pointer to be const). If you want different behavior for an object, you need to declare the object to have that specific behavior.

    0 讨论(0)
  • 2020-11-29 06:47

    Just because Image is const in your imaginary constructor doesn't mean that what m_data points to is. You'd end up being able to assign a "pointer to const" to a "const pointer to non-const" inside your class, which would remove constness without a cast. This would obviously allow you to violate invariants and couldn't be allowed.

    As far as I know, any specific sets of const-ness that are needed can be accurately and completely specified within the current standard.

    Another way to look at it is that const means the method doesn't mutate your object's state. A constructor's sole purpose is to initialize an object's state to be valid (well hopefully anyway - any constructors with side effects should be ...carefully evaluated).

    EDIT: In C++ constness applies to both members, and for pointers and references, to the accessible constness of the referred object. C++ consciously made the decision to split out these two different const-nesses. Firstly, do we agree that this code demonstrating the difference should compile and print out "non-const"?

    #include <iostream>
    
    struct Data
    {
        void non_const() { std::cout << "non-const" << std::endl; }
    };
    
    struct Image
    {
         Image(             Data & data ) : m_data( data ) {}
    
         void check() const { m_data.non_const(); }
         Data & m_data;
    };
    
    int main()
    {
        Data data;
        const Image img(data);
        img.check();
    
        return 0;
    }
    

    So then in order to obtain the behavior where it could accept a const-ref and store it as const-ref, the effective declaration of the reference would have to change to be const. This would then mean that it would be a completely distinct type, NOT a const version of the original type (since two types with members differing in const-qualification are treated as two separate types in C++). Thus, either the compiler has to be able to do excessive behind-the-scenes magic to convert these things back and forth, remembering the const-ness of the members, or it has to treat it as a separate type which then couldn't be used in place of the normal type.

    I think what you're trying to achieve is a referencee_const object, a concept that only exists in C++ as a separate class (which I suspect could be implemented with judicious use of templates although I didn't make an attempt).

    Is this strictly a theoretical question (answer: C++ decided to split object and referencee constness) or is there an actual practical uncontrived problem you're trying to solve?

    0 讨论(0)
  • 2020-11-29 06:56

    Const objects should initialize their member variables and a const constructor wouldn't be able to do so.

    0 讨论(0)
  • 2020-11-29 06:58

    In my opinion, the fact of that the ctors haven't return-type specification is what fails here. Any other imaginable syntax like for example

    class A
    {
        const A& ctor(...);
    }
    

    would be, imho, very valuable. For example, imagine such a situation of calling a method with prototype

    void my_method(const my_own_string_class& z);
    

    If my_own_string_class holds a ctor from char*, the compiler could choose this ctor, but as this ctor is not allowed to return a const object, it need to allocate and copy... If const return type were allowed, one could do

    class my_own_string_class
    {
        char *_ptr;
        public:
        const my_own_string_class& ctor(char *txt)
        : _ptr(txt)
        { return *this;}
     }
    

    provided that this special construct be restricted to the creation of temporal instances. (And dtor's must be mutable ;) ).

    0 讨论(0)
  • 2020-11-29 07:03

    Mark B goes over the fundamental considerations, but note that you can do something similar in pure C++. Consider:

    struct Data { };
    
    class ConstImage {
    protected:
      const Data *const_data;
    public:
      ConstImage (const Data *cd) : const_data(cd) { }
      int getFoo() const { return const_data->getFoo(); }
    };
    
    class Image : public ConstImage {
    protected:
      Data *data() { return const_cast<Data *>(const_data); }
    public:
      Image(Data *d) : const_data(d) { }
      void frob() { data()->frob(); }
    };
    

    Instead of using const Image *, use ConstImage *, and there you go. You could also simply define a static function pseudo-constructor:

    const Image *Image::newConstImage(const Data *d) {
      return new Image(const_cast<Data*>(d));
    }
    

    This, of course, relies on the programmer to ensure that there aren't any const functions which might somehow mutate the pointed-to Data's state.

    You can also combine these techniques:

    class Image {
    protected:
      const Data *const_data;
      Data *data() { return const_cast<Data *>(const_data); }
    public:
      void frob() { data()->frob(); }
      int getFoo() const { return const_data->getFoo(); }
    
      Image(Data *d) : const_data(d) { }
    
      static const Image *newConst(const Data *cd) {
        return new Image(const_cast<Data *>(cd));
      }
    };
    

    This gets the best of both worlds; since data() is a non-const member, you have static checking for mutation of the pointed-to value. You also, however, have a const constructor, and can directly cast between Image * and const Image * (ie, you can remove the constness if you know it is safe).

    You can also abstract away the separation of pointers further:

    template<typename T>
    class ConstPropPointer {
    private:
      T *ptr;
    public:
      ConstPropPointer(T *ptr_) : ptr(ptr_) { }
      T &operator*() { return *ptr; }
      const T &operator*() const { return *ptr; }
      T *operator->() { return ptr; }
      const T *operator->() const { return ptr; }
    };
    
    
    class Image {
    protected:
      ConstPropPointer<Data> data;
    public:
      void frob() { data->frob(); }
      int getFoo() const { return data->getFoo(); }
    
      Image(Data *d) : data(d) { }
    
      static const Image *newConst(const Data *cd) {
        return new Image(const_cast<Data *>(cd));
      }
    };
    

    Now, if this is const, data becomes const, propagating that into *data as well. Good enough for you? :)

    I suppose the final answer is probably this: In order for a const constructor to be useful and safe, we'd need something like the ConstPropPointer you see there built into the language. Const constructors would then be allowed to assign from const T * to constprop T *. This is more complex than it sounds - for example, how does this interact with template classes such as vector?

    So, this is a somewhat complex change, but the problem doesn't seem to come up all that much. More importantly, there's a simple workaround here (the ConstPropPointer can be librarized, and the static pseudo-constructor is simple enough to add). So the C++ committee probably passed it over for more important things, if it was even proposed at all.

    0 讨论(0)
提交回复
热议问题