对于我的库,我想公开一个干净的公共(public) API,它不会分散实现细节的注意力。但是,正如您所了解的那样,这些细节甚至会泄露到公共(public)领域:一些类具有有效的公共(public)方法,可供库的其余部分使用,但对 API 的用户来说不是很有用,因此不要不需要成为其中的一部分。公共(public)代码的简化示例:
class Cookie;
class CookieJar {
public:
Cookie getCookie();
}
class CookieMonster {
public:
void feed(CookieJar cookieJar) {
while (isHungry()) {
cookieJar.getCookie();
}
}
bool isHungry();
}
CookieJar 的 getCookie() 方法对库的用户没有用处,他们大概不喜欢 cookie。然而,CookieMonster 使用它来喂养自己,当给定一个时。
有一些习语可以帮助解决这个问题。 Pimpl 惯用语提供了隐藏类的私有(private)成员的方法,但几乎没有掩饰不应成为 API 一部分的公共(public)方法。也可以将它们移动到实现类中,但是您需要提供对它的直接访问以供库的其余部分使用。这样的标题看起来像这样:
class Cookie;
class CookieJarImpl;
class CookieJar {
public:
CookieJarImpl* getImplementation() {
return pimpl.get();
}
private:
std::unique_ptr<CookieJarImpl> pimpl;
}
如果您真的需要阻止用户访问这些方法,它会很方便,但如果这只是一种烦恼,这就没什么用了。事实上,新方法现在比上一个更无用,因为用户无权访问 CookieJarImpl 的实现。
另一种方法是将接口(interface)定义为抽象基类。这可以明确控制公共(public) API 的一部分。任何私有(private)细节都可以包含在该接口(interface)的实现中,用户无法访问。需要注意的是,由此产生的虚拟调用会影响性能,甚至比 Pimpl 习惯用法更严重。更简洁的 API 的交易速度对于应该是高性能库的东西来说并不是很有吸引力。
为了详尽无遗,另一种选择是将有问题的方法设为私有(private),并在需要从外部访问它们的地方使用友元类。然而,这也使目标对象也可以访问真正的私有(private)成员,从而在某种程度上破坏了封装。
到目前为止,对我来说最好的解决方案似乎是 Python 方式:不要试图隐藏实现细节,只需适本地命名它们,这样它们就很容易被识别为不是公共(public) API 的一部分,并且不会分散常规用法。想到的命名约定是使用下划线前缀,但显然这样的名称是为编译器保留的,不鼓励使用它们。
是否有任何其他 C++ 命名约定来区分不打算从库外使用的成员?或者您会建议我使用上述替代方案之一还是我错过的其他方案?
最佳答案
回答我自己的问题:这个想法基于接口(interface) - 实现关系,其中公共(public) API 被明确定义为接口(interface),而实现细节驻留在扩展它的单独类中,用户无法访问,但可以访问图书馆的其余部分。
在使用 CRTP 实现静态多态性的过程中,作为 πìντα ῥεῖ 建议避免虚拟调用开销,我意识到对于这种设计实际上根本不需要多态性,只要只有一种类型会实现接口(interface)。这使得任何类型的动态调度都毫无意义。在实践中,这意味着扁平化所有你从静态多态性中获得的丑陋模板,并以非常简单的东西结束。没有 friend ,没有模板,(几乎)没有虚拟电话。让我们将其应用于上面的示例:
这是 header ,仅包含带有示例用法的公共(public) API:
class CookieJar {
public:
static std::unique_ptr<CookieJar> Create(unsigned capacity);
bool isEmpty();
void fill();
virtual ~CookieJar() = 0 {};
};
class CookieMonster {
public:
void feed(CookieJar* cookieJar);
bool isHungry();
};
void main() {
std::unique_ptr<CookieJar> jar = CookieJar::Create(20);
jar->fill();
CookieMonster monster;
monster.feed(jar.get());
}
这里唯一的变化是将 CookieJar 变成一个抽象类并使用工厂模式而不是构造函数。
实现:
struct Cookie {
const bool isYummy = true;
};
class CookieJarImpl : public CookieJar {
public:
CookieJarImpl(unsigned capacity) :
capacity(capacity) {}
bool isEmpty() {
return count == 0;
}
void fill() {
count = capacity;
}
Cookie getCookie() {
if (!isEmpty()) {
count--;
return Cookie();
} else {
throw std::exception("Where did all the cookies go?");
}
}
private:
const unsigned capacity;
unsigned count = 0;
};
// CookieJar implementation - simple wrapper functions replacing dynamic dispatch
std::unique_ptr<CookieJar> CookieJar::Create(unsigned capacity) {
return std::make_unique<CookieJarImpl>(capacity);
}
bool CookieJar::isEmpty() {
return static_cast<CookieJarImpl*>(this)->isEmpty();
}
void CookieJar::fill() {
static_cast<CookieJarImpl*>(this)->fill();
}
// CookieMonster implementation
void CookieMonster::feed(CookieJar* cookieJar) {
while (isHungry()) {
static_cast<CookieJarImpl*>(cookieJar)->getCookie();
}
}
bool CookieMonster::isHungry() {
return true;
}
总体而言,这似乎是一个可靠的解决方案。它强制使用工厂模式,如果您需要复制和移动,则需要以与上述类似的方式自己定义包装器。这对于我的用例来说是可以接受的,因为无论如何我需要使用它的类都是重量级资源。
我注意到的另一件有趣的事情是,如果你觉得真的很冒险,你可以用 reinterpret_casts 替换 static_casts 并且只要接口(interface)的每个方法都是你定义的包装器,包括析构函数,你就可以安全地将任意对象分配给一个你定义的接口(interface)。可用于制作不透明 wrapper 和其他恶作剧。
关于C++ API 设计 : Clearing up public interface,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38855926/
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server
我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数
我有用于控制用户任务的Rails5API项目,我有以下错误,但并非总是针对相同的Controller和路由。ActionController::RoutingError:uninitializedconstantApi::V1::ApiController我向您描述了一些我的项目,以更详细地解释错误。应用结构路线scopemodule:'api'donamespace:v1do#=>Loginroutesscopemodule:'login'domatch'login',to:'sessions#login',as:'login',via::postend#=>Teamroutessc
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:
我正在使用Mandrill的RubyAPIGem并使用以下简单的测试模板:testastic按照Heroku指南中的示例,我有以下Ruby代码:require'mandrill'm=Mandrill::API.newrendered=m.templates.render'test-template',[{:header=>'someheadertext',:main_section=>'Themaincontentblock',:footer=>'asdf'}]mail(:to=>"JaysonLane",:subject=>"TestEmail")do|format|format.h
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU
我正在尝试使用Ruby2.0.0和Rails4.0.0提供的API从imgur中提取图像。我已尝试按照Ruby2.0.0文档中列出的各种方式构建http请求,但均无济于事。代码如下:require'net/http'require'net/https'defimgurheaders={"Authorization"=>"Client-ID"+my_client_id}path="/3/gallery/image/#{img_id}.json"uri=URI("https://api.imgur.com"+path)request,data=Net::HTTP::Get.new(path