`const`对象的构造函数

时间:2018-03-07 11:58:22

标签: c++ constructor arguments const

考虑以下代码:

#include <iostream>

class test
{
public:
    test( char *arg )
    :   _arg( arg )
    {}  
    char *_arg;
};

int main( )
{
    char *txt1       = "Text one";  // Ignore this warning.
    const char *txt2 = "Text two";

    test       t1( txt1 );  // Normal case, nothing new.
    const test t2( txt2 );  // Since object is const, I'd like to be able to pass a const argument in.
}

它因错误而爆炸:

error: invalid conversion from ‘const char*’ to ‘char*’ [-fpermissive]
   const test t2( txt2 );

然后我尝试添加第二个构造函数来测试编译器是否可以为t2 const对象选择正确的构造函数:

    test( const char *arg )
    :   _arg( arg )
    {}  

然后错误是:

error: invalid conversion from ‘const char*’ to ‘char*’ [-fpermissive]
  : _arg( arg )

因此将我的对象声明为const并不会使其字段const

如何正确声明我的类构造函数,以便它可以处理const和非const对象的创建?

7 个答案:

答案 0 :(得分:2)

您遇到的问题会更深入。它表明了设计问题。

您希望仅公开部分API。您说对于$ tree -I node_modules . ├── __tests__ │   └── index.js ├── bar.js ├── foo.js ├── index.js ├── package.json └── yarn.lock 对象,您只会调用const个方法,对于const,您可以执行任何操作。但这是有问题的。

您接受non-const对象从const限定符中删除它,并且不会通过静默契约调用非const方法。或者,您需要限制方法,这样可以使用不同的对象 - 类型。

C#库通过提供包围原始对象的有限接口并仅公开const方法来实现它。像这样:

const

它基本上是一个只读代理。

答案 1 :(得分:1)

  

如何正确声明我的类构造函数,以便它可以处理const和非const对象的创建?

在构造期间,对象不是const ,或者构造函数无法初始化对象(因为所有数据成员都是{{1} })。

因此,构造函数不能进行const限定,并且您不能使用const个对象的构造函数重载。

现在,您可以重载参数,但您的数据成员在构建期间始终具有类型const,尽管它有资格char *不是 char * const)在const限定的测试实例中使用。

选项包括:

  1. 在参数类型上重载构造函数,并始终存储const char * 。如果您通过char *,则必须复制它(并且您负责了解您拥有并且必须取消分配此内存)

    在此方案中,您依赖于保持指针私有并使用const限定访问器来停止通过const限定对象更改指针的内容。

    同样,您需要手动执行此操作,因为const char *char * const的类型不同,因为指向类型的常量与指针的常量无关:具有你的类的const实例只是阻止你改变指针,而不是它指向的字符。

  2. 重载构造函数并始终存储const char *。这样可以避免复制,但如果您有时需要更改指向字符,则显然无法正常工作

  3. 只需编写不同的mutable-string和immutable-string类

  4. 如果有帮助,请考虑以下事项:

    const char *

    并注意

    template <typename T> struct test {
        T* p_;
        test(T *p) : p_(p) {}
    };
    template <typename T> test<T> mktest(T *p) { return {p}; }
    

    提供const char *ccp = "immutable characters in a string literal"; char *cp = strdup(ccp); auto a = mktest(ccp); auto b = mktest(cp); 类型atest<const char>类型b并且这些类型不相同,不可转换,并且不再具有相关性对于任何其他类型test<char>,语言而不是test<T>

答案 2 :(得分:1)

编译器不关心您在运行时对对象的实际操作。

Const之所以有效,是因为编译器会在编译时禁止某些可能会更改对象的内容。在某些情况下,这可能会过度限制,因为编译器通常无法全面了解程序中发生的情况。

例如,在const对象上调用非const成员函数:即使成员函数实际上没有改变对象的状态,编译器仍然会禁止它,因为非const函数可以可能会改变对象。

在您的示例中类似:即使您没有更改该类的特定const实例的成员,也可能在某处有其他同一类的非const实例,这就是为什么它会拒绝构造来自const对象的类的任何实例。

如果你想要一个保证不改变其成员的类,那就是不同的类型

class test
{
public:
    test( char *arg )
    :   _arg( arg )
    {}  
    char *_arg;
};

class immutable_test
{
public:
    immutable_test(char const* arg)
        :_arg(arg)
    {}

    char const* _arg;
};

int main( )
{
    char *txt1       = "Text one";  // Ignore this warning.
    const char *txt2 = "Text two";

    test           t1( txt1 );
    immutable_test t2( txt2 );
}

答案 3 :(得分:1)

注意事项:对于用例不佳的用例,这是一个很长的答案。然而,长答案的原因和主要重点是:

  • 展示并解释什么是 不可能
  • 提供一种方法,使编译器可以根据参数的常数来决定 是否创建保持const对象,并保留该信息,即使对象被传递。这非常接近OP请求。

如其他答案所述,您可以有两个构造函数,一个用于const参数,另一个用于non-const,但是两个构造函数都只会创建一个对象,该对象可以是constnon-const。所以这没有帮助。

另一种方法可能是拥有一个 工厂方法 ,该方法将根据参数的恒定性创建const对象或非const对象。尽管这听起来似乎很有希望,但是不允许保留两者之间差异的语义,如以下伪代码所示:

// creating an object holding a const, using the factory method `create`:
const MyClass const_holder  = create(const_param);

// however you cannot prevent this:
MyClass non_const_holder = create(const_param);

在第二种情况下,工厂将在要复制或移动的 const 对象上方创建(或直接将其创建为具有复制删除的non_const_obj,因为C ++ 17是强制复制删除)。除非您删除复制并移动,否则您将无法采取任何措施来避免第二种情况,在这种情况下,第一种情况也不会起作用,并且万事万物都会崩溃。

因此,如果不创建实际上两个不同的类型,就不可能保留使用了哪个构造函数的信息,并且避免将用const param创建的对象分配给不使用该类型的对象。

但是,实际上并不需要打扰用户,因为有两个类,如果使用适当的模板实现,则用户可以使用简单的代码,从而感觉到游戏中只有一个类,例如:

// creating a const holder with a factory method
// the type would *remember* that it is holding a const resource
// and can act accordingly
auto const_holder = create(const_param);

// and you can also create a non const holder with the same factory
auto non_const_holder = create(param);

将允许这些操作:

// (a) calling a non const method on the content of the non const holder
non_const_holder->non_const_method();

// (b) assigning a non const holder into a const holder object
//     -- same as assigning char* to const char*
const_holder = non_const_holder;

不允许这些操作:

// (a) calling a non const method on the content of a const holder
const_holder->non_const_method(); // should be compilation error

// (b) assigning a const holder into a non const holder object
//     -- same as one cannot assign const char* to char*
non_const_holder = const_holder; // should be compilation error

在某种程度上,这与propagate_const ...

的想法非常相似

代码将具有工厂方法:

template<class T>
Pointer<T> create(T* t) {
    return Pointer<T>::create(t);
}

还有指针类模板的两个实现。

基本模板:

template<class T>
class Pointer {
    T* ptr;
    Pointer(T* p) : ptr(p) {}
    friend class Pointer<const T>;
public:
    // factory method
    static Pointer create(T* p) {
        return p;
    }
    operator T*() { return ptr; }
    operator const T*() const { return ptr; }
};

和const版本的专用版本:

template<class T>
class Pointer<const T> {
    const T* ptr;
    Pointer(const T* p) : ptr(p) {}
public:
    Pointer(const Pointer<T>& other) {
        ptr = other.ptr;
    }
    // factory method
    static const Pointer create(const T* p) {
        return p; 
    }
    operator const T*() { return ptr; }
    operator const T*() const { return ptr; }
};

主要外观如下:

int main() {
    char str[] = "hello";
    const char* const_str = "hello";
    // non-const - good!
    auto s1 = create(str);
    // const holding non-const - ok!
    const auto s2 = create(str);
    // non-const that holds const - good!
    auto s3 = create(const_str);
    // also possible: const holding const
    const auto s4 = create(const_str);

    s1[4] = '!'; // OK
    // s2[0] = '#'; // obviously doesn't compile - s2 is const
    // s3[0] = '@'; // doesn't compile - which is good - s3 holds const!
    // s4[0] = 'E'; // obviously doesn't compile - s4 is const
    // avoids assignment of s3 that holds a const into s1 that holds a non-const
    // s1 = s3; // <= doesn't compile - good!
    s3 = s1; // allows assignment of `holding-non-const` into `holding-const`
    s3 = s2; // allows assignment of `holding-non-const` into `holding-const`
    s3 = s4; // allows assignment of `holding-const` into `holding-const`
}

代码:http://coliru.stacked-crooked.com/a/4729be904215e4b2

答案 4 :(得分:0)

无法完成。仅仅因为对象是常量,它并不意味着它保证char*不会被用来修改它的值。构造函数可以改变参数,并且类外部的代码可以在暴露时修改其内容。请考虑以下示例。

struct Test {
    char* buffer;

    Test(char* buffer):
        buffer(buffer)
    {
        buffer[0] = 'a';
    }

    char* get() const {
        return buffer;
    }
};

int main(int argc, char* argv[]) {
    std::string s{ "sample text" };

    const Test t(s.data());

    t.get()[1] = 'b';
    t.buffer[2] = 'c';

    std::cout << s << '\n';
}

以上打印abcple text,即使t是常量。即使它永远不会修改它的成员,它确实会改变指向的字符串,它也允许外部代码修改它。这就是const个对象无法接受const char*个参数来初始化其char*成员的原因。

答案 5 :(得分:0)

编译器阻止你不小心丢弃const ... ...

</body>

答案 6 :(得分:0)

有趣。 查看下面的示例(对象myclass2),以了解const对象不一定提供所需的保护!

#include <iostream>
#include <cctype>
#include <cassert>

class MyClass
{
  public:
    MyClass(char * str1, size_t size1) : str{str1}, size{size1}
    {
    }
    char * capitalizeFirstChar()
    {
        *str = std::toupper(*str);
        return str;
    }
    char nthChar(size_t n) const
    {
        assert(n < size);
        return str[n];
    }

    char * str;
    size_t size;
};


int main()
{
    {
        static char str1[] = "abc";
        MyClass myclass1(str1, sizeof(str1) / sizeof(*str1));

        myclass1.capitalizeFirstChar();
        std::cout << myclass1.nthChar(0) << std::endl;
    }
    std::cout << "----------------------" << std::endl;
    {
        static const char str2[] = "abc";
        //             UGLY!!! const_cast
        const MyClass myclass2(const_cast<char *>(str2), sizeof(str2) / sizeof(*str2));

        // myclass2.capitalizeFirstChar();        // commented: will not compile

        std::cout << myclass2.nthChar(0) << std::endl;

        char c = 'x';
        // myclass2.str = &c;                     // commented: will not compile
                                                  // The const myclass2, does not
                                                  // allow modification of it's members

        myclass2.str[0] = 'Z';  // WILL COMPILE (!!) and should cause a segfault
                                // The const myclass2, CANNOT PROTECT THE OBJECT POINTED TO by str
                                // Reason: the member in MyClass is 
                                //                      char *str  
                                //         not 
                                //                const char *str

        std::cout << myclass2.nthChar(0) << std::endl;
    }
}

好的,str成员问题实际上是最好的解决方法,只需将成员设为私有即可。

那丑陋的const_cast呢? 解决此问题的一种方法是拆分为Const基类,并派生非const行为(使用const-casts)。也许像这样:

#include <iostream>
#include <cctype>
#include <cassert>

class MyClassConst
{
  public:
    MyClassConst(const char * str1, size_t size1) : str{str1}, size{size1}
    {
    }
    char nthChar(size_t n) const
    {
        assert(n < size);
        return str[n];
    }

    const char * str;
    size_t size;
};

class MyClass : public MyClassConst
{
  public:
    MyClass(char * str1, size_t size1) : MyClassConst{const_cast<const char *>(str1), size1}
    {
    }
    char * capitalizeFirstChar()
    {
        char * cp = const_cast<char *>(str);
        *cp       = std::toupper(*cp);
        return cp;
    }
};


int main()
{
    {
        static char str1[] = "abc";
        MyClass myclass1(str1, sizeof(str1) / sizeof(*str1));
        myclass1.capitalizeFirstChar();
        std::cout << myclass1.nthChar(0) << std::endl;
    }
    std::cout << "----------------------" << std::endl;
    {
        static const char str2[] = "abc";
        //             NICE: no more const_cast
        const MyClassConst myclass2(str2, sizeof(str2) / sizeof(*str2));

        // a.capitalizeFirstChar();               // commented: will not compile

        std::cout << myclass2.nthChar(0) << std::endl;

        char c = 'x';
        // myclass2.str = &c;                     // commented: will not compile
                                                  // The const myclass2, does not
                                                  // allow modification of it's members

        // myclass2.str[0] = 'Z';                 // commented: will not compile

        std::cout << myclass2.nthChar(0) << std::endl;
    }
}

值得吗?依靠。 一种是在类外交易const-cast ...在类内 进行const-cast。因此maby用于库代码(包括丑陋的内部代码和静态代码分析异常),并且外部使用干净,这是匹配项...