使用 std::set 和 shared_ptr 优化 C++ 代码
Optimizing C++ code with std::set and shared_ptr
我们的老师正在教我们一些优化技术,为此她给了我们以下 C++ 代码来优化。
另一点是重载的Add
函数,我想优化它,但我认为它正确地完成了它的工作。
所以我很困惑,还有什么地方可以优化?
FStructA
和 FStructB
共享一个共同的成员 Key
,因此可以考虑将其移动到它们都派生自的基础 class。
在处理多态类型时,基本 class 几乎总是需要一个 virtual
析构函数,以便在通过基本 class 指针销毁对象时正确调用派生构造函数。这在使用智能指针时尤其重要,例如在多态对象的容器中,就像这段代码所做的那样。
struct
成员默认为 public
,struct
派生自另一个 struct
,默认使用 public
继承,因此明确说明 FStructA
和 FStructB
从 IStruct
继承 public
是多余的。
使用override
时,不需要再指定virtual
。 override
就其本质而言意味着 virtual
。
避免将 new
与智能指针构造函数一起使用。使用 std::make_...()
函数 - std::make_unique()
用于 std::unique_ptr
,std::make_shared()
用于 std::shared_ptr()
.
重载的 Add()
方法可以合并到一个模板方法中。
emplace_back()
在处理(智能)指针时与 push_back()
相同,尤其是对于必须在容器外部构造的多态类型。只有当容器本身能够直接构造元素时,使用 emplace_back()
而不是 push_back()
才有意义,然后您只需提供 emplace_back()
构造函数参数。但在这段代码中情况并非如此。
在你的 range-for
循环中,你正在 DataElement
按值 ,这将在 [=] 中复制每个 shared_ptr
43=],因此在它们进入和离开作用域时递增和递减它们的引用计数。虽然这很好,但这是不必要的开销。您应该改为参考 DataElement
。事实上,在迭代任何容器时,您通常应该使用引用,除非您确实需要副本,或者制作副本的开销可以忽略不计(即,使用普通类型)。
尽可能考虑在声明变量时使用 auto
,尤其是在处理模板类型时。这在 range-for
循环中特别有用。
不要将 static_cast
与 shared_ptr
的原始指针一起使用,而是使用 static_pointer_cast
以维护适当的共享所有权语义。
不要不必要地复制对象。您取消引用 static_cast
的结果,然后制作对象的副本,然后处理副本,而不是原始对象。这有时很有用,但这段代码不是其中之一。使用指针或引用来避免复制。
除非确实需要,否则不要将基础 class 转换为派生 class。如果您有多态类型(正如这段代码所做的那样),请尽可能使用多态行为(即虚方法)。正确的多态处理逻辑不应该关心它在什么类型上运行。
当 std::unique_ptr
就足够时,不要使用 std::shared_ptr
。 std::shared_ptr
具有 std::unique_ptr
没有的开销(引用计数、控制块等)。共享所有权有其用途,但此代码并未展示它们。
话虽如此,试试这样的东西:
struct IStruct
{
std::string Key;
virtual ~IStruct() = default;
//virtual bool GetType() const = 0;
virtual void Process() = 0;
};
struct FStructA : IStruct
{
int IntData;
/*
bool GetType() const override
{
return true;
}
*/
void Process() override
{
// Process A
}
};
struct FStructB : IStruct
{
float FloatData;
/*
bool GetType() const override
{
return false;
}
*/
void Process() override
{
// Process B
}
};
class SomeSystem
{
public:
template<typename T, typename... Args>
void Add(Args&&... args)
{
DataElements.push_back(/*std::make_shared*/std::make_unique<T>(std::forward<Args>(args)...));
}
void Process()
{
for (auto &DataElement : DataElements)
{
/*
if (DataElement->GetType())
{
auto StructA = static_pointer_cast<FStructA>(DataElement);
// Process A
}
else
{
auto StructB = static_pointer_cast<FStructB>(DataElement);
// Process B
}
*/
DataElement->Process();
}
}
private:
std::vector</*std::shared_ptr*/std::unique_ptr<IStruct>> DataElements;
};
int main()
{
SomeSystem system;
system.Add<FStructA>(...params as needed...);
system.Add<FStructB>(...params as needed...);
system.Process();
}
我们的老师正在教我们一些优化技术,为此她给了我们以下 C++ 代码来优化。
另一点是重载的Add
函数,我想优化它,但我认为它正确地完成了它的工作。
所以我很困惑,还有什么地方可以优化?
FStructA
和 FStructB
共享一个共同的成员 Key
,因此可以考虑将其移动到它们都派生自的基础 class。
在处理多态类型时,基本 class 几乎总是需要一个 virtual
析构函数,以便在通过基本 class 指针销毁对象时正确调用派生构造函数。这在使用智能指针时尤其重要,例如在多态对象的容器中,就像这段代码所做的那样。
struct
成员默认为 public
,struct
派生自另一个 struct
,默认使用 public
继承,因此明确说明 FStructA
和 FStructB
从 IStruct
继承 public
是多余的。
使用override
时,不需要再指定virtual
。 override
就其本质而言意味着 virtual
。
避免将 new
与智能指针构造函数一起使用。使用 std::make_...()
函数 - std::make_unique()
用于 std::unique_ptr
,std::make_shared()
用于 std::shared_ptr()
.
重载的 Add()
方法可以合并到一个模板方法中。
emplace_back()
在处理(智能)指针时与 push_back()
相同,尤其是对于必须在容器外部构造的多态类型。只有当容器本身能够直接构造元素时,使用 emplace_back()
而不是 push_back()
才有意义,然后您只需提供 emplace_back()
构造函数参数。但在这段代码中情况并非如此。
在你的 range-for
循环中,你正在 DataElement
按值 ,这将在 [=] 中复制每个 shared_ptr
43=],因此在它们进入和离开作用域时递增和递减它们的引用计数。虽然这很好,但这是不必要的开销。您应该改为参考 DataElement
。事实上,在迭代任何容器时,您通常应该使用引用,除非您确实需要副本,或者制作副本的开销可以忽略不计(即,使用普通类型)。
尽可能考虑在声明变量时使用 auto
,尤其是在处理模板类型时。这在 range-for
循环中特别有用。
不要将 static_cast
与 shared_ptr
的原始指针一起使用,而是使用 static_pointer_cast
以维护适当的共享所有权语义。
不要不必要地复制对象。您取消引用 static_cast
的结果,然后制作对象的副本,然后处理副本,而不是原始对象。这有时很有用,但这段代码不是其中之一。使用指针或引用来避免复制。
除非确实需要,否则不要将基础 class 转换为派生 class。如果您有多态类型(正如这段代码所做的那样),请尽可能使用多态行为(即虚方法)。正确的多态处理逻辑不应该关心它在什么类型上运行。
当 std::unique_ptr
就足够时,不要使用 std::shared_ptr
。 std::shared_ptr
具有 std::unique_ptr
没有的开销(引用计数、控制块等)。共享所有权有其用途,但此代码并未展示它们。
话虽如此,试试这样的东西:
struct IStruct
{
std::string Key;
virtual ~IStruct() = default;
//virtual bool GetType() const = 0;
virtual void Process() = 0;
};
struct FStructA : IStruct
{
int IntData;
/*
bool GetType() const override
{
return true;
}
*/
void Process() override
{
// Process A
}
};
struct FStructB : IStruct
{
float FloatData;
/*
bool GetType() const override
{
return false;
}
*/
void Process() override
{
// Process B
}
};
class SomeSystem
{
public:
template<typename T, typename... Args>
void Add(Args&&... args)
{
DataElements.push_back(/*std::make_shared*/std::make_unique<T>(std::forward<Args>(args)...));
}
void Process()
{
for (auto &DataElement : DataElements)
{
/*
if (DataElement->GetType())
{
auto StructA = static_pointer_cast<FStructA>(DataElement);
// Process A
}
else
{
auto StructB = static_pointer_cast<FStructB>(DataElement);
// Process B
}
*/
DataElement->Process();
}
}
private:
std::vector</*std::shared_ptr*/std::unique_ptr<IStruct>> DataElements;
};
int main()
{
SomeSystem system;
system.Add<FStructA>(...params as needed...);
system.Add<FStructB>(...params as needed...);
system.Process();
}