7

可以说我有以下内容:

class A { virtual void g() = 0 }

class B : public A { virtual void g() { ... } }
class C : public A { virtual void g() { ... } }

... f(bool x)
{
  if (x) { return B(); } else { return C(); }
}

bool get_boolean();

int main()
{
  bool b = get_boolean();
  ... x = f(b);
  x.g();
}

无论如何都可以在没有调用的情况下执行上述操作new,即仅在堆栈上?

4

10 回答 10

7

避免动态分配的一种简单方法是使用静态分配,这与动态分配尽可能相反。但是,必须小心执行,因为即使使用非线程程序,也可能会无意中陷入代码的两个或多个部分各自认为他们“拥有”某个静态分配的对象的情况。更糟糕的是,这些本质上是全局变量(即使伪装成单例,或者在下面的代码中作为局部静态变量)本质上充当了意大利面条式通信的中心枢纽,在那里,引发混乱的信息在你无法想象的地方之间自由传播,完全没有你的控制。

所以,静态分配方案有一些缺点...... :-)

但让我们从那里开始:

// Using static allocation.

#include <iostream>
using namespace std;

struct A { virtual void g() = 0; };

struct B : A { virtual void g() override { wcout << "A\n"; } };
struct C : A { virtual void g() override { wcout << "B\n"; } };

A& f( bool const x )
{
    static B    theB;
    static C    theC;

    if( x ) { theB = B(); return theB; } else { theC = C(); return theC; }
}

bool get_boolean() { return false; }

int main()
{
    bool const b = get_boolean();
    A& x = f( b ); 
    x.g();
}

为了避免静态分配方案的错误所有权缺陷,您可以提供堆栈上的存储,使用 C++自动分配(C++ 自动分配根据定义是一个堆栈,一种 LIFO 分配方案)。但这意味着将存储向下传递给函数。然后该函数可以返回对相关对象的引用:

// Using automatic storage (the stack)

#include <iostream>
using namespace std;

struct A { virtual void g() = 0; };

struct B : A { virtual void g() override { wcout << "A\n"; } };
struct C : A { virtual void g() override { wcout << "B\n"; } };

A& f( bool const x, B& b, C& c )
{
    if( x ) { b = B(); return b; } else { c = C(); return c; }
}

bool get_boolean() { return false; }

int main()
{
    bool const b = get_boolean();
    B   objBStorage;
    C   objCStorage;
    A&  x   = f( b, objBStorage, objCStorage ); 
    x.g();
}

但是,即使我们选择忽略诸如带有副作用的构造等问题,即当我们轻率地假设类B并且C被设计为与这样的方案很好地配合时,上述内容也会浪费存储。如果BC实例很大,则可以考虑使用 C++ 的工具在预先存在的存储中构造对象,称为放置新。由于内存对齐问题,在 C++03 中正确执行有点困难,但 C++11 提供了更好的支持,如下所示:

#include <iostream>
#include <memory>           // unique_ptr
#include <new>              // new
#include <type_traits>      // aligned_storage
using namespace std;

typedef unsigned char Byte;

struct A { virtual void g() = 0; };

struct B : A { virtual void g() override { wcout << "A\n"; } };
struct C : A { virtual void g() override { wcout << "B\n"; } };

A* f( bool const x, void* storage )
{
    return (x? static_cast<A*>( ::new( storage ) B() ) : ::new( storage ) C());
}

bool get_boolean() { return false; }

void destroyA( A* p ) { p->~A(); }

int main()
{
    enum{ enoughBytes = 
        (sizeof( B ) > sizeof( C ))? sizeof( B ) : sizeof( C ) };
    typedef aligned_storage< enoughBytes >::type StorageForBOrC;

    bool const b = get_boolean();
    StorageForBOrC storage;
    A* const pX = f( b, &storage );
    unique_ptr<A, void(*)(A*)> const cleanup( pX, destroyA );
    pX->g();
}

现在,我会选择以上哪个?

我会选择严格限制但简单且即时的静态分配,还是选择浪费内存的自动分配,或者……优化但有些复杂的就地对象构造?

答案是,我一个都不选!

我不会专注于微观效率,而是专注于清晰度正确性,因此只需承受动态分配的性能损失。为了正确起见,我将对函数结果使用智能指针。如果事实证明这真的会减慢速度,我可能会考虑使用专用的小对象分配器

总之,不要为小事烦恼!:-)

于 2012-11-12T11:36:22.983 回答
3

在函数f对象中B()或者C()都是临时的,所以你只能f按值返回它们。

也许boost::variant适合你。然后,您甚至不需要虚拟方法或从公共基类派生。

于 2012-11-12T08:54:42.820 回答
0

多态性也适用于引用,但您不能通过引用返回临时值,因此:

A& f(bool x)
{
  static B b;
  static C c;
  if (x) { return b; } else { return c; }
}

A& x = f(b);
于 2012-11-12T08:54:34.600 回答
0

无论如何,是否可以在不调用 new 的情况下执行上述操作,即仅在堆栈上?

你可以只使用Placementnew。这允许您为您的对象指定一个内存位置(例如,在char您在堆栈上声明的缓冲区中)。


示例: http: //www.parashift.com/c++-faq-lite/placement-new.html

于 2012-11-12T08:57:24.163 回答
0

我会重构它以使用函数参数:

class A { virtual void g() = 0 }

class B : public A { virtual void g() { ... } }
class C : public A { virtual void g() { ... } }

template<typename FunOb>
typename std::result_of<FunOb(A&)>::type f(bool x, FunOb fo)
{
  if (x) { B b; return fo(b); } else { C c; return fo(c); }
}

bool get_boolean();

int main()
{
  bool b = get_boolean();
  f(b, [](A& x) { x.g(); } );
}
于 2012-11-12T08:58:03.237 回答
0

我在这个问题中有什么遗漏吗?为什么不像下面这样简单

void CallGFunc(A *obj){
   obj->g();
}

int main(){
    ....
    B b;
    C c;

    CallGFunc(booleanvar? &b:&c)
    ....
}

这是你想要的吗?

于 2012-11-12T09:00:00.537 回答
0

您可以使用对象引用,但不能返回对在堆栈中创建的对象的引用,因为调用完成后该对象将被删除。

这里有一个建议:

#include <iostream>

class A
{
public:
virtual void g() const = 0;
};

class B : public A
{
public:
virtual void g() const {std::cout << "B" << std::endl;};
};

class C : public A
{
public:
virtual void g() const {std::cout << "C" << std::endl;};
};

const A &f(bool b)
{
  if (b) return B(); else return C();
}

void doStuff(const A &a)
{
  a.g();
}

int main(void)
{
  doStuff(B()); //"B"
  doStuff(C()); //"C"

//  A &a = f(true); //ERROR! a will be deleted right after call is made, giving you a reference to an invalid object!

return 0;
}

另一种解决方案是返回静态对象的引用,请记住该对象将由调用该方法的所有实例共享。效果很好,您不必修改对象,否则这不是一个好的解决方案。

#include <iostream>

class A
{
public:
virtual void g() const = 0;
};

class B : public A
{
public:
virtual void g() const {std::cout << "B" << std::endl;};
};

class C : public A
{
public:
virtual void g() const {std::cout << "C" << std::endl;};
};

const A &f(bool b)
{
static B ret1;
static C ret2;
   if (b) return ret1; else return ret2;
}


void doStuff(const A &a)
{
  a.g();
}

int main(void)
{
  doStuff(f(true)); //"B"
  doStuff(f(false)); //"C"

return 0;
}
于 2012-11-12T09:11:38.310 回答
0

并不真地。问题是复制操作不是多态的;如果您的函数返回一个A,那么只会A复制一个。(这称为切片。)如果没有复制,您需要对象的任意生命周期,而不是基于范围的生命周期。通常唯一可用的其他可能性是动态分配。

如果函数只被调用一次,并且对象或多或少是一个单例,您可以使用静态实例,尽管您可能希望在单独的范围内定义它们,以避免系统地构造它们:

A*
f( bool c )
{
    A* results = NULL;
    if ( c ) {
        static B b;
        results = &b;
    } else {
        static C c;
        results = &c;
    }
    return results;
}

当然,总是有将复制与多态性结合起来的信封惯用语。但这在实践中会导致更多的动态分配,即使它们都发生在“幕后”,所以客户端代码看不到它们。(如果对象是不可变的,字母信封习惯用法可以使用引用计数指针,并避免大多数额外的分配。)

于 2012-11-12T10:27:49.200 回答
0

如果您真的想将所有内容都保留在堆栈上,最好编写一个处理A接口的函数,然后在您想要的对象上调用它:

void actionsWithA(A& a){
    a.g();
}

void doItWithB(){
    B b;
    actionsWithA(b);
}

void doItWithC(){
    C c;
    actionsWithA(c);
}

bool get_boolean();

int main()
{
    bool b = get_boolean();
    if(b) { doItWithB(); } else { doItWithC(); }
}
于 2012-11-12T13:00:48.397 回答
0

当然。这很简单。

class Fighter {
public:
  virtual const char *attack() const { return "no power"; }
};

class Ryu : public Fighter {
public:
  const char *attack() const override { return "Hadoken"; }
};

class Guile : public Fighter {
public:
  const char *attack() const override { return "Alec full"; }
};

int main() {
  Fighter *ryu_dynamic = new Ryu;
  Fighter *guile_dynamic = new Guile();
  cout << ryu_dynamic->attack() << endl;
  cout << guile_dynamic->attack() << endl;
  delete ryu_dynamic;
  delete guile_dynamic;

  const Fighter &ryu_static_1 = Ryu();
  const Fighter &guile_static_1 = Guile();
  cout << ryu_static_1.attack() << endl;
  cout << guile_static_1.attack() << endl;

  Fighter &&ryu_static_2 = Ryu();
  Fighter &&guile_static_2 = Guile();
  cout << ryu_static_2.attack() << endl;
  cout << guile_static_2.attack() << endl;

  Fighter *ryu_static_3 = &Ryu();
  Fighter *guile_static_3 = &Guile();
  cout << ryu_static_3->attack() << endl;
  cout << guile_static_3->attack() << endl;

  return 0;
}

输出

Hadoken
Alec 完整
的 Hadoken
Alec 完整 的

于 2020-04-16T23:21:19.590 回答