考虑以下代码:
#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对象的创建?
答案 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限定的测试实例中使用。
选项包括:
在参数类型上重载构造函数,并始终存储const char *
。如果您通过char *
,则必须复制它(并且您负责了解您拥有并且必须取消分配此内存)
在此方案中,您依赖于保持指针私有并使用const限定访问器来停止通过const限定对象更改指针的内容。
同样,您需要手动执行此操作,因为const char *
与char * const
的类型不同,因为指向类型的常量与指针的常量无关:具有你的类的const实例只是阻止你改变指针,而不是它指向的字符。
重载构造函数并始终存储const char *
。这样可以避免复制,但如果您有时需要更改指向字符,则显然无法正常工作
只需编写不同的mutable-string和immutable-string类
如果有帮助,请考虑以下事项:
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);
类型a
,test<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
参数,另一个用于non-const
,但是两个构造函数都只会创建一个对象,该对象可以是const
或non-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`
}
答案 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用于库代码(包括丑陋的内部代码和静态代码分析异常),并且外部使用干净,这是匹配项...