粗略地说,我有一个包含const unsigned char数组的类。 此类的对象由特殊的工厂函数创建,该函数还负责构造数组(在堆上)。在工厂函数中创建对象时,将为其指定数组。数组不会被复制,对象将只使用给定的指针。在销毁时,它将释放数组占用的内存块。
class Foo
{
private:
const unsigned char* array;
size_t size;
public:
Foo(const unsigned char* array, size_t size) : array(array), size(size) {}
~Foo() {delete [] array;}
};
Foo* factoryFunction(const void* data, size_t size)
{
unsigned char* array = new unsigned char[size];
memcpy(array, data, size);
return new Foo(array, size);
}
现在我想知道是否有任何副作用,因为new []
会返回unsigned char *
,但会为delete []
调用const unsigned char *
。我不会遇到任何分段错误。
答案 0 :(得分:5)
这很好,标准中的非规范性文本暗示了同样的事情:
[C++11: 5.3.5/2]:
如果操作数具有类类型,则通过调用上述转换函数将操作数转换为指针类型,并使用转换后的操作数代替原始操作数以用于其余部分部分。在第一个替代方法(删除对象)中,delete
的操作数的值可以是空指针值,指向由前一个 new创建的非数组对象的指针-expression ,或指向表示此类对象的基类的子对象(1.8)的指针(第10条)。如果不是,则行为未定义。 在第二种方法(删除数组)中,delete
的操作数值可能是空指针值或由前一个数组 new产生的指针值-expression 。如果不是,则行为未定义。 [注意:这意味着 delete-expression 的语法必须与new
分配的对象类型相匹配,而不是的语法新的表达。 -end note] [注意:指向const类型的指针可以是 delete-expression 的操作数;在将指针表达式用作 delete-expression 的操作数之前,不必丢弃指针表达式的constness(5.2.11)。 -end note]
以下段落可能存在争议:
[C++11: 5.3.5/3]:
在第一个备选方案(删除对象)中,如果要删除的对象的静态类型与其动态类型不同,则静态类型应为基类要删除的对象的动态类型和静态类型应具有虚拟析构函数或行为未定义。 在第二种选择中(删除 array )如果要删除的对象的动态类型与其静态类型不同,则行为未定义。
虽然这篇文章的意图在我看来只是处理多态,但结合以下段落,它可能被解释为,严格来说,你的代码实际上调用未定义的行为:
[C++11: 3.9.3/1]:
[..] 类型的cv限定版或cv非限定版是不同类型; [..]
但是,我有理由相信这可以被视为标准中的措辞缺陷;我的意图似乎很清楚。
同样,这条规则甚至可以暗示程序不会编译:
[C++11: 12.5.4]:
[..] 如果delete-expression以一元::
运算符开头,则在全局范围内查找释放函数的名称。否则,如果 delete-expression 用于释放静态类型具有虚拟析构函数的类对象,则释放函数是在动态类型的虚拟析构函数的定义点处选择的函数(12.4) 。否则,如果 delete-expression 用于释放类T
的对象或其数组,对象的静态和动态类型应相同,在T的范围内查找释放函数的名称。如果此查找未能找到该名称,则在全局范围中查找该名称。如果查找结果不明确或无法访问,或者查找选择了放置重新分配函数,则程序格式不正确。
但同样,这不是规则的意图,它在没有虚拟析构函数的情况下解决多态性,并且在多个类声明中存在真正的歧义。
总而言之,在解释这些规则的措辞时,你最好的选择仍然是我们开始时的非规范但非常明确的说明。
答案 1 :(得分:1)
不应该是任何问题,因为“unsigned char”(或任何数据类型)上的“const” - 使其成为只读数据。
在删除中使用“[]”表示必须删除数组。
要了解“删除”与“删除[]”背后的内容,请阅读this。
答案 2 :(得分:1)
首先,你应该避免在c ++中使用这种数组,而应该使用std::vector<unsigned char>
代替。
关于const
关键字。它只是告诉编译器和读取代码的人不应该更改此成员/变量/参数。
使用指针,const
关键字的位置非常重要:
(编辑:找到了我借用此部分的Greyson的答案)
const
关键字将其左侧的部分标记为常量(如果它位于开头,则表示const unsigned char
和unsigned char const
相等的类型
要将指针标记为const,请执行此操作(内容仍可更改):
unsigned char * const aConstantPointerToAMutableContent;
要将内容标记为const,您将执行以下操作:
const unsigned char * aPointerToAConstantContentA;
unsigned char const * aPointerToAConstantContentB;
将两者标记为常数:
const unsigned char * const aConstantPointerToAConstantContent;
这是编译器和用户的提示,因此很清楚数据的完成或未完成。如果参数为const unsigned char *
,则用户将知道如果传递该内容将不会更改。
因为const
关键字只是一个标记,如果某些内容可以改变,它对大小本身没有影响,所以对于delete
它应该没有效果。 (请参阅Lightness Races in Orbit的答案和评论,以及“Is const_cast safe?”可能适用于介入)。
但我不喜欢你的代码是构造函数是公共的。我可以直接调用它,但因为参数是const unsigned char* array
我不希望该类改变内容或者在销毁时删除它。 (我不希望直接创建对象的行为与使用工厂的方式不同。)
所以我将工厂方法作为类的static
方法,并使构造函数protected
。