std::make_unique<char>(size_t size) 和 std::make_unique<char[]>(size_t size) 之间的区别是什么?

4
我正在实现循环数组数据结构,其代码如下:
struct CircularArrayException : public std::exception {
    std::string msg;

    CircularArrayException(const std::string arg_msg) 
    : msg{"CircularArrayException: " + arg_msg} {}

    const char * what () const throw () {
        return msg.c_str();
    }
};

template <typename T>
class CircularArray {
public:
    const size_t array_size;
    std::unique_ptr<T> uptr_arr;

    size_t occupied_size = 0;
    int front_idx = -1;
    int back_idx = -1;
 
    CircularArray(const CircularArray& ca) = delete;

    CircularArray& operator=(const CircularArray& ca) = delete;

    CircularArray(
        const size_t arg_array_size
    ):  array_size{arg_array_size} {
        uptr_arr = std::make_unique<T>(array_size);
    };
};

实现后,我使用了 CircularArray<char> 进行了测试并且它正常运行。 但是,然后我意识到我们使用的是 std::make_unique<char[]>(num_elements) 声明一个指向数组的 unique_ptr,而不是 std::make_unique<char>(num_elements)。但即使如此,代码似乎仍然能够正常工作。我查看了 std::make_unique 的文档 这里,但无法理解第 (2) 个签名的解释。有人可以帮我理解其中的区别,以及为什么我的代码可以工作吗?
以下是 cppreference 对于第 (2) 个签名的说明:
template< class T >
unique_ptr<T> make_unique( std::size_t size );

(2) (自C++14起) (仅适用于具有未知边界的数组类型)

构造一个未知边界的 T 数组。只有当 T 是具有未知边界的数组时,该重载才参与重载解析。函数等价于: unique_ptr<T>(new typename std::remove_extent<T>::type[size]())

这里是Goldbolt链接: https://godbolt.org/z/K9h3qTeTW


1
你是否在像Valgrind这样的内存调试器中运行了它?无论如何,我认为make_unique()不需要大小参数,因此该参数仅用于初始化创建的对象。只需使用调试器逐步执行代码或研究cppreference.com上的文档即可找出答案。顺便说一句:当你可以直接使用vector时,我会质疑你使用动态分配的选择。考虑在codereview.stackexchange.com上提交你的代码(一旦它正常工作)。 - Ulrich Eckhardt
你的问题中有太多代码了!尝试提供一个最小化且易于复制的代码,只包含两个唯一指针 :) 这将更激励人们阅读和回答你的问题 :))) - Ivan
感谢您的评论。我会简化问题。 - Kishore Kaushal
“vector” 是更好的选择,我会使用它。但是,我仍然想知道 make_unique 的 (2) 签名是什么意思。 - Kishore Kaushal
2
@UlrichEckhardt 如果类型是具有未知边界的数组,则make_unique需要大小参数。 - n. m.
1个回答

5

std::make_unique<char>(65);创建一个指向初始值为65'A')的单个字符的指针。std::make_unique<char[]>(65)创建一个包含65个元素的数组。

如果您运行此代码:

#include <memory>
#include <iostream>

int main()
{
    auto a = std::make_unique<char>(65);
    std::cout << *a << "\n";
    auto b = std::make_unique<char[]>(65);
    std::cout << (int)b[0] << "\n";
}

在第一个例子中,它将打印A,而在第二个例子中,将会打印未定义的值(可能是0),因为数组元素未初始化。

你的代码“运行”是靠运气,使用超过一个元素的“数组”将会导致未定义的行为。


它正常工作。这是链接:https://godbolt.org/z/K9h3qTeTW - Kishore Kaushal
不,它不会。正如Alan所解释的那样 - 你正在写入你没有拥有的内存。之所以不会崩溃,只是因为你写的字符串非常短。如果你写了超过几个字符,你的代码就会崩溃:https://godbolt.org/z/e18rGbsPv - Chronial
3
C和C++因设计而闻名于内存不安全。 - Peter - Reinstate Monica
1
是的,C++ 不会进行边界检查。如果你只写了少量越界的数据,可能不会覆盖任何重要的内容,你的代码看起来可以正常工作。但是,如果再多写几个字节,你的代码很可能会开始崩溃或者更糟糕的是默默地出现错误。 - Alan Birtles
谢谢大家。我现在知道我在做什么了。这是一个很好的学习机会。学到的教训:在C++中,你可以访问未拥有的内存。XD - Kishore Kaushal
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接