call stack是什么错误_软件特攻队|API 设计,C++不容忽视的错误设计(3)

9f4ae2fe58e0dca68a759aa57729394b.png

对于许多C ++开发人员来说,API设计可能会在其优先级列表中排名第3或第4。大多数开发人员都倾向于使用C ++来获得原始功能和控制权。因此,性能和优化的想法占据这些开发者的时间的百分之八十。

当然,每个C ++开发人员都会考虑头文件设计的各个方面,但是API设计不仅仅是头文件设计那样。事实上,我强烈建议每一个开发人员在其API的设计上,无论是面向公共还是面向内部,都给予一些帮助,因为这样可以节省你大量的维护成本,提供平滑的升级路径,并为你的客户节省麻烦。

错误#7:不将只读数据/方法标记为constnoexcept

为什么这是一个错误?

有时,你的API会将来自客户端的一些数据结构作为输入。将方法和方法参数标记为const表示客户端将以只读模式使用该数据。相反,如果你没有将API方法和参数标记为const,那么你的客户可能倾向于向你传递数据副本,因为你没有做出此类保证。根据客户端代码调用API的频率,性能影响的结果可以从轻微到严重。

如何解决这个问题?

当你的API需要对客户端数据进行只读访问时,请将API方法和/或参数标记为const。假设你需要一个函数来只检查两个坐标是否相同。

//Don't do this:

bool AreCoordinatesSame(vector& vect1, vector& vect2);

相反,将方法标记为const,以便客户端知道你不会修改客户端传入的矢量对象。

bool AreCoordinatesSame(vector& vect1, vector& vect2) const;

Const正确性是一个很大的话题 - 请参考一本好的C ++教科书或阅读https://isocpp.org/wiki/faq/const-correctness中的FAQ部分。

错误#8:通过const引用返回API的内部

为什么这是一个错误?

从表面上看,通过const引用返回一个对象似乎是双赢的。这是因为:

  • 避免不必要的复制。

  • 客户端无法修改数据,因为它是const引用

但是,这可能会导致一些棘手的问题 ——即:

  1. 如果客户端API在内部解除分配后保留并使用引用,该怎么办?

  2. 什么是客户端使用const转换来抛弃对象的常量并修改它?

如何解决这个问题?

遵循三步规则:

  • 首先,尽量不要通过更好的设计来暴露API对象的内部

  • 如果规则1太贵,请考虑按值返回对象(创建副本)。

  • 如果这是一个堆分配的对象,请考虑通过shared_pointer返回它,以确保即使你的核心对象被释放也可以访问该引用。

错误#9:使用隐式模板实例化时,使用模板实现细节来混淆公共头文件

在隐式实例化中,模板代码的内部必须放在头文件中。没有其他办法。但是,你可以将模板声明(你的API用户将引用)从模板实例化中分离出来,方法是将实例化放在单独的头文件中,如下所示:

// File: Stack.h ( Public interface)

#pragma once

#ifndef STACK_H

#define STACK_H

#include

template

class Stack

{

public:

void Push(T val);

T Pop();

bool IsEmpty() const;

private:

std::vector mStack;

};

typedef Stack IntStack;

typedef Stack DoubleStack;

typedef Stack<:string> StringStack;

// isolate all implementation details within a separate header

#include "stack_priv.h"

#endif

// File: Stack_priv.h ( hides implementation details of the Stack class)

#pragma once

#ifndef STACK_PRIV_H

#define STACK_PRIV_H

template

void Stack::Push(T val)

{

mStack.push_back(val);

}

template

T Stack::Pop()

{

if (IsEmpty())

{

return T();

}

T val = mStack.back();

mStack.pop_back();

return val;

}

template

bool Stack::IsEmpty() const

{

return mStack.empty();

}

#endif

许多高质量的基于模板的API使用此技术,例如各种Boost头文件。它的好处是保持主要公共头文件不受实现细节的影响,同时将内部细节的必要暴露,隔离到明确指定为包含私有细节的单独头文件。

错误#10:当用例已知时,不使用显式模板实例化头文件

为什么这是一个错误?

从API设计的角度来看,隐式实例化受到以下问题的困扰:

  • 编译器现在负责在适当的位置滞后地实例化代码,并确保只存在该代码的一个副本以防止重复符号的链接错误。这会对你的客户端的构建和链接时间造成影响。

  • 你的代码逻辑的内部现在暴露出来,这绝不是一个好主意。

  • 客户端可以用一些你以前没有测试过的任意类型来实例化你的模板,并且会遇到奇怪的失败。

如何解决这个问题?

如果你知道你的模板将只与int、double和string一起使用,你可以使用显式实例化为这三种类型生成模板特化。它缩短了客户端的构建时间,使你不必密封模板中未经测试的类型,并将模板代码逻辑隐藏在cpp文件中。

要做到这一点很简单 - 只需按照以下三个步骤进行:

步骤1:将堆栈模板代码的实现移动到cpp文件中:

在这一点上,让我们尝试实现并使用堆栈的push()方法:

Stack myStack;

myStack.Push(31);

我们会遇到一个连接错误:

error LNK2001: unresolved external symbol "public: void __thiscall Stack::Push(int)" (?Push@?$Stack@H@@QAEXH@Z)

这是链接器告诉我们它在任何地方都找不到push方法的定义。难怪,因为我们还没有实例化它。

步骤2:在cpp文件底部创建int、double和string类型的模板实例:

// explicit template instantiations

template class Stack;

template class Stack;

template class Stack<:string>;

现在你可以构建和运行堆栈代码了。

步骤3:通过将以下typedef放在头文件的末尾,告诉客户端你的API支持int、double和string的三种限定类型:

typedef Stack IntStack;

typedef Stack DoubleStack;

typedef Stack<:string> StringStack;

警告:如果进行显式特殊化,客户端将无法创建更多特殊化(并且编译器也无法为客户端创建隐式实例化),因为实现细节隐藏在我们的.cpp文件中。请确保这是你的API的预期用例。

欢迎关注软件特攻队!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值