开发者

Why does C++ not have a const constructor?

开发者 https://www.devze.com 2023-03-25 22:30 出处:网络
(Edit: Heavy change because previous example was flawed, which may make some answers/comments seem odd)

(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 is legal because of lack of const constructor:

class Cheater
{
public:
    Cheater(int avalue) 
       : cheaterPtr(this) //conceptually odd legality in const Cheater ctor
       , value(avalue) 
    {}

    Cheater& getCheaterPtr() const {return *cheaterPtr;}
    int value;

private:
    Cheater * cheaterPtr;
};

int main()
{
    const Cheater cheater(7); //Initialize the value to 7

    cheater.value                 = 4;    //good, illegal
    cheater.getCheaterPtr().value = 4;    //oops, legal

    return 0;
}

It seems like providing a const constructor a thing would be as easy technically as const methods, and be analogous to a const overload.

Note: I'm not lookin开发者_C百科g for 'Image( const Data & data ) const' but rather 'const Image( const Data & data) const'

So:

  • Why is the const constructor absent in C++?

Here's some related material for context:

  • http://www.open-std.org/jtc1/sc22/wg21/docs/papers/1995/N0798.htm
  • How to deal with initialization of non-const reference member in const object?
  • C++, Classes, Const, and strange syntax


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?


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.


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.


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 ;) ).


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

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号