为什么以及何时通过指针在 C++ 中传递 class 类型?
Why and when to pass class types in C++ by pointer?
考虑以下代码:
class Abstract{
public:
virtual void printStuff()=0;
};
class Derived: public Abstract{
public:
void printStuff(){
printf("Stuff\n");
}
};
现在,假设我想创建一个使用 Abstract class 中的 printStuff 方法的函数。在我了解到在 C++ 中只有一种方法是可能的之前,我认为会有两种方法:一种不太明显的指针,一种更明显的方法,类似于您期望对 int、chars 等做的事情:
void ptr_function(Abstract* abs){ //non-obvious
abs->printStuff();
}
void non_ptr_function(Abstract abs){ //obvious, analogous to, say, pow(a,b)
abs.printStuff();
}
现在,我明白了第二个在C++中是被禁止的。但是,我并不真正理解这种设计的根本原因。除了作为参数传递的指针与实际对象之外,上述函数看起来不一样吗?
作为后续问题:构建必须 "contain" 其他抽象 classes 作为字段之一的 classes 的首选方法是什么?如果这个问题的答案也是:"pointers",那么我是否遗漏了什么,或者我是否必须自己跟踪这些对象的生命周期(即手动删除它们)?对于非抽象 classes 这不是问题,就好像我不使用指针一样,那么只要该对象超出范围,它就会自动删除(调用析构函数等)。但是,如果我必须使用指针,那么微观管理似乎会花费很多不必要的时间和代码。
有没有更好的方法来解决这个问题?
您不能按值传递抽象 class,因为它无法实例化。在大多数情况下,按值传递无论如何都是错误的(或者至少是非最佳的)。您正在寻找的是通过引用传递:
void ref_function(Abstract & abs)
{
abs.printStuff();
}
当您通过引用传递时,在 ref_function
中对 abs
所做的任何修改都将应用于函数外部存在的同一实例。理想情况下,在您的测试用例中,您希望将对象作为 const Abstract & abs
传递,这将防止对该对象进行任何更改。但是在您的示例中,您需要将 printStuff
标记为 const 以表示它不会更改调用它的对象 - 签名将更改为 virtual void printStuff() const
回答你关于抽象 classes 的所有权应该如何工作的其他问题.. 请记住你实际上不能拥有抽象 class 的实例,所以你是谈论的是通过指向其抽象基础 class 的句柄持有指向某个派生对象的指针。您可能想为此使用 std::unique_ptr,因为当您的 class 被销毁时,它会正确删除拥有的对象。
class Abstract
{
public:
virtual void Foo() = 0;
};
class Derived : public Abstract
{
public:
virtual void Foo() override {}
};
class MyClass
{
public:
MyClass();
private:
std::unique_ptr<Abstract> myObject;
};
MyClass::MyClass() : myObject(std::make_unique<Derived>())
{
}
将对象传递给函数实际上有五种不同的可能性:
//Function declarations
void passByValue(Derived o); //Not possible with an abstract class.
void passByReference(Abstract& o);
void passByConstReference(const Abstract& o);
void passByPointer(Abstract* o);
void passByConstPointer(const Abstract* o);
//Function calls
Derived o;
passByValue(o);
passByReference(o); //May change o!
passByConstReference(o);
passByPointer(&o); //May change o.
passByConstPointer(&o);
因为从passByReference(o)
的调用中看不到调用可能会修改变量o
,所以我从不使用按引用传递。我总是使用 pass-by-const-reference 或 pass-by-pointer。
然而,如今许多 C++ 程序员普遍厌恶使用指针,因为他们不能很好地使用像 std::unique_ptr<>
和 std::shared_ptr<>
这样的智能指针。如果你用这些智能指针管理你的对象,你必须总是传递智能指针而不是一个裸体的 pointer/reference,通常通过 const-reference:
void passBySmartPointer(const std::shared_ptr<Abstract>& o);
void passConstBySmartPointer(const std::shared_ptr<const Abstract>& o);
使用这种方法,您永远不会在代码中看到裸指针...
(之所以需要传智能指针,是因为智能指针的链条一定不能断:如果你把一个智能指针转成裸指针,再转回智能指针,第二个智能指针指针不知道第一个,你会遇到麻烦。还有其他方法可以避免这种情况,但这超出了这个答案的范围。)
考虑以下代码:
class Abstract{
public:
virtual void printStuff()=0;
};
class Derived: public Abstract{
public:
void printStuff(){
printf("Stuff\n");
}
};
现在,假设我想创建一个使用 Abstract class 中的 printStuff 方法的函数。在我了解到在 C++ 中只有一种方法是可能的之前,我认为会有两种方法:一种不太明显的指针,一种更明显的方法,类似于您期望对 int、chars 等做的事情:
void ptr_function(Abstract* abs){ //non-obvious
abs->printStuff();
}
void non_ptr_function(Abstract abs){ //obvious, analogous to, say, pow(a,b)
abs.printStuff();
}
现在,我明白了第二个在C++中是被禁止的。但是,我并不真正理解这种设计的根本原因。除了作为参数传递的指针与实际对象之外,上述函数看起来不一样吗?
作为后续问题:构建必须 "contain" 其他抽象 classes 作为字段之一的 classes 的首选方法是什么?如果这个问题的答案也是:"pointers",那么我是否遗漏了什么,或者我是否必须自己跟踪这些对象的生命周期(即手动删除它们)?对于非抽象 classes 这不是问题,就好像我不使用指针一样,那么只要该对象超出范围,它就会自动删除(调用析构函数等)。但是,如果我必须使用指针,那么微观管理似乎会花费很多不必要的时间和代码。
有没有更好的方法来解决这个问题?
您不能按值传递抽象 class,因为它无法实例化。在大多数情况下,按值传递无论如何都是错误的(或者至少是非最佳的)。您正在寻找的是通过引用传递:
void ref_function(Abstract & abs)
{
abs.printStuff();
}
当您通过引用传递时,在 ref_function
中对 abs
所做的任何修改都将应用于函数外部存在的同一实例。理想情况下,在您的测试用例中,您希望将对象作为 const Abstract & abs
传递,这将防止对该对象进行任何更改。但是在您的示例中,您需要将 printStuff
标记为 const 以表示它不会更改调用它的对象 - 签名将更改为 virtual void printStuff() const
回答你关于抽象 classes 的所有权应该如何工作的其他问题.. 请记住你实际上不能拥有抽象 class 的实例,所以你是谈论的是通过指向其抽象基础 class 的句柄持有指向某个派生对象的指针。您可能想为此使用 std::unique_ptr,因为当您的 class 被销毁时,它会正确删除拥有的对象。
class Abstract
{
public:
virtual void Foo() = 0;
};
class Derived : public Abstract
{
public:
virtual void Foo() override {}
};
class MyClass
{
public:
MyClass();
private:
std::unique_ptr<Abstract> myObject;
};
MyClass::MyClass() : myObject(std::make_unique<Derived>())
{
}
将对象传递给函数实际上有五种不同的可能性:
//Function declarations
void passByValue(Derived o); //Not possible with an abstract class.
void passByReference(Abstract& o);
void passByConstReference(const Abstract& o);
void passByPointer(Abstract* o);
void passByConstPointer(const Abstract* o);
//Function calls
Derived o;
passByValue(o);
passByReference(o); //May change o!
passByConstReference(o);
passByPointer(&o); //May change o.
passByConstPointer(&o);
因为从passByReference(o)
的调用中看不到调用可能会修改变量o
,所以我从不使用按引用传递。我总是使用 pass-by-const-reference 或 pass-by-pointer。
然而,如今许多 C++ 程序员普遍厌恶使用指针,因为他们不能很好地使用像 std::unique_ptr<>
和 std::shared_ptr<>
这样的智能指针。如果你用这些智能指针管理你的对象,你必须总是传递智能指针而不是一个裸体的 pointer/reference,通常通过 const-reference:
void passBySmartPointer(const std::shared_ptr<Abstract>& o);
void passConstBySmartPointer(const std::shared_ptr<const Abstract>& o);
使用这种方法,您永远不会在代码中看到裸指针...
(之所以需要传智能指针,是因为智能指针的链条一定不能断:如果你把一个智能指针转成裸指针,再转回智能指针,第二个智能指针指针不知道第一个,你会遇到麻烦。还有其他方法可以避免这种情况,但这超出了这个答案的范围。)