- 主题:现代C++中,比较大的结构体一般怎么使用容器?
比如下面这样的:
struct TradeRecord
{
int bar_id; //交易发生的行情数据的位置
int trade_count; //交易数量,负数为卖出
double trade_price; //交易价格
int total_count; //交易后总数量
double total_cost ; //交易后累积历史成本
double profit; //本次交易利润,买入时统一为0
double cash ; //本次交易后剩余现金
};
传统写法,就是用指针,vector<TradeRecord*> , 不涉及任何对象拷贝、赋值。非常简单直观。
但是现代c++不提倡直接使用指针,那该怎么使用容器呢?
要定义各种赋值拷贝函数吗? 但是依然不可避免发生对象拷贝?
这种应用场景,怎么做效率高,又符合现代c++理念?
--
FROM 223.72.44.*
有办法直接在vector上创建新对象吗?
外面创建的对象,放入vector的时候,总要发生一次拷贝。
另外这种很多简单字段的结构体,move语义对它也没啥用。
所以,比较的就是,栈上创建发生一次拷贝的开销大,还是堆上创建并复制指针的开销大?
【 在 Bernstein 的大作中提到: 】
: 你那指针总要分配内存的吧,现在放vector里就不用分配内存了,其他的地方用到的话,用指针指向vector内部即可(有悬挂指针的可能)
:
--
FROM 223.72.40.*
我原来的方案就是直接用二维数组(double[size][7])放全部数据,结构体都没用
一行就是一个记录,应该没有比这个开销最小了。
这不是想在不太影响性能的情况下,让程序更“现代”些。
我没怎么用过C++ 11及以后的C++,所以来请大牛们指导。
【 在 foliver 的大作中提到: 】
: c++程序员对于性能的考虑简直到了变态的地步了。而且各种教条主义盛行。
: 如果不是计算密集程序,怎么方便怎么用。
: 计算密集程序,用什么vector啊。
: ...................
--
FROM 223.72.40.*
现在C++主推模板元编程,就是见不得指针啊
用指针了,拷贝移动就都不用了,省事很多。
C++委员会该做的,是怎么提高堆内存管理的效率,再提供个可选的gc, 而不是全部用值语义来避免指针。
值语义虽然避免了指针的问题,但是处理复制需要很多额外的维护工作。
对于数量不多的大对象是可以的。 但是对于需要定义很多结构体的应用,就不太友好。
【 在 jimmycmh 的大作中提到: 】
: 哪个现代c++不提倡使用指针了?顶多是提倡用智能指针吧,或者是搞个右值引用和move
:
--
FROM 223.72.40.*
gpt回答的真好。
我再确认下, vec.push_back(TradeRecode{ })这种写法,是直接在vector已分配的内存上构建对象,不会发生复制吧?
如果是这样,用值就很好了。
【 在 gfkid 的大作中提到: 】
: gpt4推荐直接使用值
: 在现代 C++ 中,确实鼓励避免直接使用裸指针,主要是为了提高代码的安全性和可维护性。对于您提到的 `TradeRecord` 结构,可以使用智能指针(如 `std::unique_ptr` 或 `std::shared_ptr`)来管理动态分配的对象,或者直接使用值语义(即直接存储对象本身,而不是指针)。
: ### 使用智能指针
: ...................
--
FROM 223.72.40.*
嗯,按照8字节对齐了。
c++11后,vector提供emplace_back方法,支持就地创建,拷贝、移动都不会发生。
不过这个函数直接接受构造函数的参数,不接受初始化列表,不发生隐式类型转换。
所以就有个小限制
m_records.push_back({
.bar_id = bar,
.trade_count = c,
.trade_price = price,
.total_count = rec0.total_count + c,
.total_cost = rec0.total_cost + c * price * (1 + fee),
.profit = 0,
.cash = rec0.cash - c * price * (1 + fee),
});
这样的用法就不能用了。如果要改成emplace_back,就只能按顺序提供参数,无法按照字段名称,不过问题也不大
m_records.emplace_back({
bar,
c,
price,
rec0.total_count + c,
rec0.total_cost + c * price * (1 + fee),
0,
rec0.cash - c * price * (1 + fee),
});
性能问题解决了,看着不太完美。
【 在 gfkid 的大作中提到: 】
: 感觉把字节对齐一下比较好
: 编译器很强大,也许不需要考虑那么多了,担心的话就把copy构造和copy赋值标记为delete。
--
FROM 223.72.40.*
加上这句:m_records.emplace_back(0, 0, .0, 0, .0, .0, Capital );
编译报错:“TradeRecord::TradeRecord(TradeRecord &&)”: 尝试引用已删除的函数
把TradeRecord的移动构造函数delete之后,emplace_back 编译通不过
定位到一个叫construct_at的内部函数,难道emplace_back还是要调用到移动构造?
那这个函数就没有意义了。
【 在 finlab 的大作中提到: 】
: 嗯,按照8字节对齐了。
: c++11后,vector提供emplace_back方法,支持就地创建,拷贝、移动都不会发生。
: 不过这个函数直接接受构造函数的参数,不接受初始化列表,不发生隐式类型转换。
: ...................
--
修改:finlab FROM 223.72.40.*
FROM 223.72.40.*
这里按行访问和按列访问的情况都有,还不好说什么方式更合适。
【 在 dilemma 的大作中提到: 】
: [size][7]是aos, array of structures
: 如果遍历的时候不会使用每个元素的所有数据,
: 用[7][size],即soa, structure of arrays性能更好,可以提高缓存命中率
: ...................
--
FROM 223.72.40.*
我在vc 2022上,选择c++20, emplace_back 不接受初始化列表。 只能:
m_records.emplace_back(
bar,
c,
price,
rec0.total_count + c,
rec0.total_cost + c * price * (1 + fee),
0,
rec0.cash - c * price * (1 + fee)
);
我的理解,使用初始化列表,是在调用emplace_back之前,就使用隐式转换构建对象,然后必然发生移动和拷贝。
而emplace就是为了避免这个,所以它直接接受一个可变长的参数列表,在内部构建对象,不发生移动和拷贝。
这时性能差异就明显了。
【 在 gfkid 的大作中提到: 】
: gpt4认为这个性能差异微不足道。
: 对于您的 `TradeRecord` 结构体,使用具名初始化和使用参数列表初始化的性能应该是非常接近的,因为在这两种情况下,涉及的操作基本相同。
: 1. **具名初始化**:
: ...................
--
FROM 223.72.40.*
对于都是简单数据字段的结构,移动跟拷贝没啥区别,反而更麻烦
我试了,没有移动构造,emplace_back编译不过,但是我提供了,它也没有调用。
倒是push_back先构造,再移动,再复制,感觉开销很大
而emplace 只调用构造。
【 在 gfkid 的大作中提到: 】
: 移动构造不用delete吧
--
FROM 223.72.40.*