【问题标题】:c++ translate from dynamic allocation to referencesc++ 从动态分配转换为引用
【发布时间】:2015-06-28 15:13:51
【问题描述】:

我有以下代码:

class A{
public:
    virtual do_something() = 0;
}

class B : public A{
public:
    virtual do_something() override;
}

void use_a(A *a){
   if (a){
      a->do_something();
      delete a;
   }
}

use_a( new B() );

如何将其转化为参考文献?
注意 do_something() 不是 const 方法。 我认为它可以是这样的:

void use_a(A &&a){
   a->do_something();
}

use_a( B() );

但有人告诉我这是不好的风格,必须避免。

【问题讨论】:

  • 不尝试最小化行数。 B b; use_a(b);
  • 我知道这行得通,但这是人们使用它的方式吗?
  • 如果将nullptr 传递给use_a 在您的应用程序逻辑中构成有效状态,则您无法将代码转换为引用。

标签: c++ pointers c++11 reference rvalue


【解决方案1】:

右值引用具有移动语义。将B 移动为A 时效果不佳。

使用左值引用:

void use_a(A &a);

B b;
use_a(b);

或模板:

template <typename T>
void use_a(T &&a);

或者,如果它不需要是一个引用,一个智能指针:

void use_a(std::unique_ptr<A> a);
void use_a(std::shared_ptr<A> a);

【讨论】:

  • 人们是这样做的吗?来自 Java 和旧 C++,很难相信没有人直接在参数列表中创建类:)
  • 好吧,我猜这个对象可能来自一些工厂函数,但我同意,它仍然可以绑定到一个变量,在发送到函数之前,即使之后没有人需要 ti。
  • @Nick 如果它来自工厂,您将使用unique_ptrshared_ptr。但是我看不到多态性在临时变量上有多大用处,lambdas 更加灵活。 (Java 在其匿名类的临时对象上使用多态性,这是我们在 C++ 中所没有的。)
  • 接受答案之前的最后一个问题 - 这是正确的方法吗? pastebin.com/YiMActsG,这不是有糖吗?
  • @Nick 我在Pair 上看不到任何多态性(list 无论如何都是左值引用)。如果确实没有,请使用右值引用。
【解决方案2】:

很简单,您可以通过提供具体实例将指针转换为引用,即取消引用:

void f(int& i);

f(*(new int)); // do not do this!

问题在于 C++ 中的原始指针正是如此——它们没有自动生命周期范围,并且通过转换为左值引用,您建议了一个实例是具体的并且不应被接收者销毁的协定。

int* ptr = new int;
f(ptr);
delete ptr; // otherwise it leaked

现代 C++ 使用 RAII 提供受控的自动生命周期管理,C++11 引入了unique_ptrshared_ptr 来处理指针。在 C++14 中,我们还拥有完全避免原始指针的机制。

std::unique_ptr<int> ptr = std::make_unique<int>(/* ctor arguments here */);
f(ptr.get());
// now when ptr goes out of scope, deletion happens automatically.

另见http://en.cppreference.com/w/cpp/memory/unique_ptr

在任何时候,只有一个std::unique_ptr 应该具有给定分配的地址(如果不是released,它将假定所有权并将delete 分配在退出范围内)。

对于引用计数指针:http://en.cppreference.com/w/cpp/memory/shared_ptr

--- 编辑 ---

基于 OPs cmets:

首先要注意

Pair p = { "one", "two" };
// and
Pair p("one", "two");
Pair p{"one", "two"};

是同义词,在所有情况下,它们通过分配堆栈空间并调用 Pair::Pair("one", "two") 以在那里构造 Pair 对象来创建堆栈局部变量 p

但是请记住,这是一个堆栈变量 - 它具有自动生命周期,并且将在当前范围结束时到期。

{ Pair p{"one", "two"}; list_add(list, p); } //p is destroyed

理论上,您可以将其替换为

list_add(list, Pair{"one", "two"});

但重要的是list_add 是否希望 保留对象,直到您将其从列表中删除...这通常是基于列表的函数需要指针所期望的.如果它需要一个非常量引用,它可能会这样做。

回答你原来的帖子::

struct A { virtual void doSomething() {} };
struct B : public A { virtual void doSomething() override() {} };

void useDoSomethingInterface(A& a) {
    a.doSomething();
}

int main() {
    A a;
    B b;
    useDoSomethingInterface(a);
    useDoSomethingInterface(b);
}

考虑以下几点:

void list_add(IList& list, Pair& pair) {
    pair.next = list.head;
    list.head = &pair; // << BAD NEWS
}

void badness(IList& list) {
    list_add(list, Pair("hello", "world"));
}

void caller() {
    IList list;
    badness(list);
    // list.head now points to a destroyed variable on the stack

C++ 中的 C 指针是原始的机器级指针。他们不参考计数。并且 C++ 对象实例具有固定的、定义良好的生命周期:直到作用域结束。

但是,如果 list_add 按值获取数据

void list_add(IList& list, Pair pair)

那我们就没事了。我们创建的临时 Pair 必须复制一次以创建 pair,然后再次复制到列表中,这很可惜,但至少不会崩溃。

【讨论】:

  • 可能我在问一些对除了我以外的所有人来说都是显而易见的事情:) 我正在尝试做这样的事情pastebin.com/YiMActsG,如果我在没有 Pair p 的情况下这样做,除非我使用 const,否则它不会编译函数中的参考。我真的需要使用所有这些局部变量吗?
  • 只要IList::add() 不存储引用,您就可以使用list_add(list, Pair("3 city", "Sofia"))。我会编辑我的答案来解释
【解决方案3】:

您的代码有点不安全。 首先,如果 a 为空怎么办?你没有检查它。
其次,如果 a 指向堆栈对象或数据段对象怎么办?您将遇到意外行为(=在大多数操作系统上崩溃)。

如果您的对象必须动态分配,只需使用std::shared_ptr

void use_a(std::shared_ptr<A>& a){
   a->do_something();
}

【讨论】:

    猜你喜欢
    • 2013-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-20
    • 1970-01-01
    • 2011-02-16
    • 2020-12-05
    • 1970-01-01
    相关资源
    最近更新 更多