8

粗略地说,我有一个包含 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 *。我没有得到任何分段错误。

4

3 回答 3

5

这很好,标准中的非规范性文本暗示了同样的事情:

[C++11: 5.3.5/2]:如果操作数具有类类型,则通过调用上述转换函数将操作数转换为指针类型,并在本节的其余部分中使用转换后的操作数代替原始操作数。在第一种选择(delete object)中,操作数的值可以是空指针值,指向由先前的new-expressiondelete创建的非数组对象的指针,或指向表示基的子对象(1.8)的指针此类对象的类别(第 10 条)。如果不是,则行为未定义。在第二种选择(删除数组)中,操作数的值可以是空指针值或由前一个数组new-expression产生的指针值。delete如果不是,则行为未定义。[注意:这意味着delete-expression的语法必须与分配的对象的类型匹配new,而不是new-expression的语法。—end note ] [ 注意:指向 const 类型的指针可以是delete-expression的操作数;在将指针表达式用作delete-expression的操作数之前,没有必要抛弃它的 constness (5.2.11) 。——尾注]


以下段落可能存在争议:

[C++11: 5.3.5/3]:在第一种选择(删除对象)中,如果要删除的对象的静态类型与其动态类型不同,则静态类型应为要删除的对象的动态类型的基类,静态类型应具有虚拟析构函数或行为未定义。在第二种选择(删除数组)中,如果要删除的对象的动态类型与其静态类型不同,则行为未定义。

虽然在我看来,这一段的意图只是处理多态性,但结合以下段落,它可能被解释为,严格来说,您的代码实际上调用了未定义的行为:

[C++11: 3.9.3/1]: [..]类型的 cv 限定或 cv 非限定版本是不同的类型;[..]

但是,我有理由相信这可以被视为标准中的措辞缺陷;我的意图似乎很清楚。


同样,这条规则甚至可能暗示程序不会编译:

[C++11: 12.5.4]: [..]如果删除表达式以一元运算符开头::,则在全局范围内查找释放函数的名称。否则,如果删除表达式用于释放静态类型具有虚拟析构函数的类对象,则释放函数是在定义动态类型的虚拟析构函数时选择的函数(12.4)。否则,如果delete-expression用于释放类T或数组的对象,则对象的静态和动态类型应相同并且在 T 的范围内查找释放函数的名称。如果此查找未能找到该名称,则在全局范围内查找该名称。如果查找的结果不明确或不可访问,或者如果查找选择了一个放置解除分配函数,则程序是非良构的。

但同样,这不是规则的意图,它解决了在没有虚拟析构函数的情况下的多态性,以及跨多个类声明的真正歧义。


总而言之,在解释这些规则的措辞时,您最好的选择仍然是我们开始使用的非规范但非常清晰的注释。

于 2013-04-19T08:27:19.653 回答
1

应该没有任何问题,因为“unsigned char”(或任何数据类型)上的“const”-ness 使其成为只读数据。

在 delete 中使用“[]”表示必须删除一个数组。

要了解“删除”与“删除 []”背后的含义,请阅读内容。

于 2013-04-19T08:13:20.907 回答
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 的答案和评论以及“ const_cast 安全吗? ”可能是为了兴趣)。

但是我不喜欢你的代码是构造函数是公共的。我可以直接调用它,但是因为参数是const unsigned char* array我不希望该类会更改内容或在销毁时将其删除。(我不希望直接创建对象会以与使用工厂不同的方式表现。)

所以我会将工厂方法作为static类的方法并制作构造函数protected

于 2013-04-19T09:01:46.920 回答