我认为我的问题听起来很愚蠢,欢迎对我投反对票。如果您在 C++ 中实现需要返回指针的方法,这样做是否安全?如果不是,为什么?
4 回答
是的。我假设您的意思是“分配并返回”一个指针。
通常具有初始化函数,它分配指向某种类型的对象的指针,然后初始化对象本身。然后由程序的不同部分来释放内存。
那么它总是取决于你在做什么。指针只是一个内存地址,因此它类似于简单地返回一个整数。您应该对指针以及如何正确实现它们进行更多研究
我觉得这个问题可能很快就会结束,但无论如何我都会尝试回答。
是的,只要你小心,它是“安全的”。事实上,这是一种非常常见的做事方式,尤其是在与 C API 交互时。话虽如此,如果可以,最好避免这样做,因为 C++ 通常提供更好的替代方案。
为什么要避免它?首先,假设您有一个如下所示的方法:
MyStruct* get_data();
返回值是指向单个实例的指针MyStruct
,还是数组的开头?你需要free()
返回的指针吗?或者也许你需要使用delete
?返回值可以是NULL
,如果是,会发生什么?如果不查看文档,您将无法了解这些内容。而且编译器也无法知道,因此它无法以任何方式帮助您。
更好的选择:
如果要返回值数组,请使用 a std::array
(如果大小在编译时固定)或 a std::vector
(如果直到运行时才知道大小)。
如果您试图避免复制大型结构,则返回一个引用,或者如果可能的话返回一个 const 引用。这样调用者就知道他们不会收到NULL
值。
如果你真的需要返回一个指针,而不是考虑使用智能指针——这将帮助你解决所有权问题。例如,std::shared_ptr
使用引用计数,并std::unique_ptr
确保给定指针只有一个所有者。
不是一个简单的问题。例如:返回指针的最佳方式。
理想情况下,您应该尽量避免返回带有副作用或义务的值。
// This may be ok, it implies no burden on the user.
Manager* GetManager();
// But what if the user decides to call delete on the value you return?
// This is not unusual in C code, but carries a hidden contract:
// I allocate - you free.
const char* GetFilename(int fd)
{
char* filename = malloc(256);
sprintf(filename, "/tmp/tmpfile.%d", fd);
return filename;
}
C++ 是关于封装和抽象的。您可以通过封装要返回的指针来编写与消费者的合同。这里的想法是,不是公开一个指针,而是公开一个负责指针所有权的对象。事实上,该语言的最新版本已经使用 std::unique_ptr、std::shared_ptr 和 std::weak_ptr 为您做到这一点。
但一个粗略、简单的 RAII 示例可能是:
class StrDupPtr
{
char* m_alloc;
public:
StrDupPtr(const char* src)
: m_alloc(strdup(src))
{}
~StrDupPtr()
{
free(m_alloc);
}
operator const char* () const { return m_alloc; }
// etc.
};
您仍然在此处返回一个指针,但您已将其封装在管理合同中,并减轻了最终用户管理资源的负担。
你不能总是避免它,当你不得不这样做时,是的,它可能很危险。
int* AllocateMeSomeMemory()
{
int* memory = malloc(4 * sizeof(int));
// here, have four ints.
return memory;
}
int main() {
int* memory = AllocateMeSomeMemory();
memory[42] = 0xDeath; // yeah, it's not a valid hex number, but that's not really the problem.
}
指针的另一个常见问题是无法知道有多少人拥有指针。这是一个人为的例子:
void transferItem(userid_t user1, userid_t user2, itemid_t item) {
Account* a1 = GetAccount(user1);
Account* a2 = GetAccount(user2);
if (a1 != a2) {
transferItemInternal(a1, a2, item);
}
delete a2;
delete a1; // Sorry Dave, I can't do that. How about a nice game of CRASH?
}
通常,a2 和 a1 会有所不同,但是当它们不同时...
另一个常见的指针失败模式是异步回调:
// ask the database for user details, call OnLoginResult with userObj when we're done.
void login(int socket, userid_t userId, passwordhash_t pass) {
User* userObj = GetUserObj(userId, socket);
Query* query = Database()->NewQuery("SELECT * FROM user WHERE id = ? AND password = ?", userId, pass);
Database()->Queue(query, OnLoginResult, userObj);
}
void OnDisconnect(int socket, int reason) {
User* userObj = GetUserBySocket(socket);
if (userObj) {
UnregisterUserObj(userObj);
delete userObj;
}
}
void OnLoginResult(void* param) {
User* userObj = static_cast<UserObj*>(param);
// all well and good unless the user disconnected while waiting.
...
}