声明指针或堆栈变量
Posted
技术标签:
【中文标题】声明指针或堆栈变量【英文标题】:declaring a pointer or a stack variable 【发布时间】:2015-08-28 12:29:00 【问题描述】:假设我们有以下情况:
如果是早上心情好,否则心情不好 打印心情可以写成:
std::string mode; //this cost us calling the constructor of std::string
if(time==Morning) // a real code should be here
mood="Good"//re assign the string
else
mood="bad"//re assign the string
std::cout << mood;
假设它不是 std::string,它是一个非常大的对象。声明“模式”并无缘无故地调用构造函数是不是太奢侈了! 使用原始指针是一种解决方案吗?这里的最佳做法是什么? 谢谢
【问题讨论】:
或std::cout << (time == Morning ? "Good" : "Bad") << std::endl;
。无需创建字符串对象。
1.每当考虑运行时成本时:首先衡量,而不是在必要时更改代码。 2. 这取决于 - 衡量在您的情况下它的真正成本(您的“昂贵”对象)
如果你的意思是像Type& ref = (condition ? obj1 : obj2);
这样的东西会比创建第三个对象的默认构造并有条件地触发复制赋值运算符更有效,很可能是的。这是否符合您的需求是一个完全不同的问题。
我不会出汗的。通过小字符串优化,空字符串的ctor只设置了几个成员。它们将以固定的偏移量在堆栈上。优化器的流分析将表明,无论采用何种分支,这些成员都被直接覆盖,因此第一个构造函数生成的所有代码都被有效地优化了。
【参考方案1】:
您可以使用ternary operator 来初始化字符串。
std::string mode = (time == Morning ? "Good" : "Bad")
正如MSalters 在 cmets 中指出的那样,这实际上仍然是两个构造(构造函数 + 副本),但它应该由编译器进行优化。可以直接用
初始化字符串std::string mode (time == Morning ? "Good" : "Bad") ;
【讨论】:
从技术上讲,它仍然是两个(转换 ctor 后跟复制 ctor),但这通常由编译器优化。直接方法是std::string mode (time == Morning ? "Good" : "Bad") ;
一点也不,*** 的内容都在知识共享许可下,正是因为我们鼓励混合贡献以获得最佳答案。【参考方案2】:
我会考虑将逻辑放入一个单独的函数中。这有助于保持函数小而简单。
std::string moodAtTime(Time time)
if(time==Morning)
// a real code should be here
return "Good";
else
return "bad";
std::string mood = moodAtTime(t);
这减少了函数长度并将代码拆分为执行简单问题的小单元。
如果只是为了初始化,可以不用命名函数,用一个lambda来做:
std::string mood = [](Time t)
if(t==Morning)
return "Good";
else
return "bad";
(now);
【讨论】:
【参考方案3】:如果您多次进入特定范围,成本会很高,是的,它可能会变得非常昂贵(取决于范围内发生的事情)。
解决方案是保留一个全局变量或更广泛使用的此类对象池(对象池/内存广泛用于此类目的,尤其是在小型和非常小的对象的情况下)。
所以,用代码来解释
// scope that is entered allot, like 100k times per second
std::string temp;
...
// magic happens
...
快速解决
static std::string g_uniqueNamed_temp_string;
...
// magic happens
...
优雅的解决方案
编辑:切换到原始指针以避免复制
std::string* tempString = StringPool::GetAPreallocatedString();
...
// magic happens
...
【讨论】:
这不是 C++ 的工作方式。 “快速解决方案”必须检查静态是否已经初始化。因为它不在堆栈上,所以必须以线程安全的方式进行检查。同步并不快。 “优雅的解决方案”采用预先分配的字符串并复制它。这比第一种情况更糟糕,第一种情况只创建一个空字符串。在现代 C++ 编译器上,这 (1) 移动堆栈指针和 (2) 将长度字段设置为零(位于堆栈上,因此在缓存中)。两个 CPU 操作都非常快。 有效点,但除了问题之外的imo!如果您仔细阅读,OP 清楚地说他只是用 std::string 举例说明,而在实际情况下,事情要复杂得多(一个非常大的对象),这让我推测它而不是 std::string有一个对象需要更复杂的初始化(例如从文件中读取)。我仍然坚持我的论点,即使用内存池、预初始化和其他技术来避免在一个范围内重复分配,并且应该在 C++ 中使用分配,这就是我所举例说明的。 您确实对“优雅”解决方案中的副本提出了一点意见,特别是考虑到我想要举例说明的技术是返回指针,而不是对象,所以我会修改我的答案。关于静态对象的同步:一个非常大的对象分配并不比变量的线程安全访问更快......假设有一个线程问题开始! 分配 20 字节还是 2000 字节对堆栈来说并不重要。并且编译器不能假设没有线程问题,它必须假设有。 好的,假设它们是,编译器会做什么?老实说,我不明白(在多线程的情况下不安全)全局变量访问怎么会比复杂的对象构造更昂贵......从你所说的我错过了一些关于编译器在下面做什么的知识访问全局变量时的引擎盖。您介意向我解释或给我一个链接或一些指示吗?【参考方案4】:您可以为此使用alloca
:
#include <iostream>
#include <sstream>
using namespace std::literals;
int main()
void *mem = alloca(sizeof(std::string)); //Allocates memory on stack; no initialization
std::string *mood;
if(true) // a real code should be here
mood = new (mem) std::string("Good");//re assign the string
else
mood=new (mem) std::string("Bad");//re assign the string
std::cout << *mood;
return 0;
【讨论】:
你的声明是一个长度为 1 的数组,是构造的。std::string mode = alloca(sizeof(std::string));
什么?!?这里有各种各样的破损......
使用alloca
分配类的实例不会调用构造函数,除非alloca
实现在内部使用new
,这充其量是值得怀疑的。因此,字符串的实例指向一个未构造的内存块,并且您有未定义的行为。如果它有效,那是纯粹的运气。见:ideone.com/19Pe5C
你没有 - std::string
构造函数永远不会在你的示例中运行,并且指针指向一个足够大的内存块以容纳一个字符串,但一个 没有包含一个构造的std::string
。赋值导致未定义的行为。
@Randl:在这种情况下,考虑 Jens 的解决方案(命名函数或 lambda)。或者,使用逗号运算符。真的,有 4 个选项比这个 hack 更好。以上是关于声明指针或堆栈变量的主要内容,如果未能解决你的问题,请参考以下文章
指针变量声明了后其默认值是啥呢,啥时候它的值才为“null”空呢?