【发布时间】:2019-12-20 18:28:08
【问题描述】:
动机
一个 CLI 应用程序尝试使用 box drawing characters。但根据标准输出设备,这可能不合适,因此有一个-t 选项来使用纯 ASCII 文本。
这个概念只是一个例子,它适用于我们想要在运行时从两组或更多组静态常量中进行选择的任何地方。
问题是什么是合理的 c++ 技术来干净利落地促进这一点。
所需的使用语法
这只是指示性的,但不断的“调用”应该很简洁
int main(int argc, char* argv[]) {
// using derived class style here, but anything would do
auto t = std::strcmp(argv[1], "-t") == 0 ? TxtTerminal() : BdTerminal();
// here we want the syntax to be short
std::cout << "text" << t.horiz << t.right_t << '\n';
}
已经尝试过
我已经尝试过 Base 和 Derived 类样式(如上),但存在一些问题。我不能在TxtTerminal 中放入一组常量,在BdTerminal 中放入一组常量(扩展TxtTerminal)。
- 使用静态常量不起作用,因为 C++ 不支持“后期静态绑定”,所以它总是使用您拥有实例的任何类的常量。
- 使用成员变量和这些类的正确实例(如上所示)也不起作用(很好),因为 Derived 类无法直接初始化 Base 类成员....
- 也不是通过它的构造函数初始化列表...
- 派生类必须将整个集合传递给基类构造函数,然后基类构造函数修改已经(BaseClass)初始化的值。真是尴尬又啰嗦。我想通过一些
struct config { .. };可能会起作用 - 类似地使用单个类,将两组常量放在
map<key,config>中,并使用getter 通过构造函数初始化bool textmode成员状态变量来检索适当的常量。 - 也许使用带有模板参数的模板化解决方案?不,因为这是一个运行时开关。
- 类似于模板的想法将是某种解决方案,其中
using别名用于 2 个单独的常量命名空间..同样的问题..编译时间..不是运行时。 - 使用
variable templates有点像 new c++20 maths constants 。同样的问题,不是运行时。
当然,所有这些const,即“画框终端”的控制字符字符串,每个都只是一个字节长。理想情况下,这些只是在可执行文件的.text (linux) 段中作为字面初始化的const char* 压缩在一起(std::string 似乎有点矫枉过正)。无论我们在哪里编写这些常量,我们都希望使用简单的concatenation 来使代码具有可读性和可维护性。 const char* 字符串文字不是那么容易吗?
许多选项,没有一个看起来很棒。我错过了一些明显的东西吗?
编辑为了回答下面的问题以及@walnut 最初的answer,这里有一些使用“静态非实例”方法的更充实的代码。
其中一些未解决的问题,见cmets:
// NOTE this code compiles and runs, but doesn't quite do what we need
// see comments
#include <cstring>
#include <iostream>
struct TextMode {
static inline const auto cls = "TM cls";
static inline const auto right_t = "TM right_t";
static inline const auto left_t = "TM left_t";
};
struct BlockDrawMode {
static inline const auto cls = "BD cls";
static inline const auto right_t = "BD right_t";
static inline const auto left_t = "BD left_t";
};
struct Terminal {
Terminal(bool textmode = true) { mode = textmode ? &text_mode : &block_drawing_mode; }
// what is this type? some parent class of TextMode/BlockDrawMode?!
// then we get the initilisation loop again...
// ??????? // could be a reference initilised in cstr initilisation, but that's a detail
const TextMode* mode;
static inline const auto text_mode = TextMode{};
static inline const auto block_drawing_mode = TextMode{};
// obviously this needs to be .. ^ BlockDrawMode
// but then doesn't compile because there is no class hierarchy ...
};
int main(int argc, char* argv[]) {
Terminal t{strcmp(argv[1], "-t") == 0};
std::cout << t.mode->cls << '\n' << t.mode->right_t << '\n';
// output (-t makes no difference right now, due to the above issues)
// TM cls
// TM right_t
return 0;
}
EDIT2:我添加了一个self answer below,它使用aggregate initialisation 来避免很多不必要的继承并发症。感觉有点“脏”,但看起来干净且工作正常?
【问题讨论】:
-
argv[1] == "-t"将总是为假,因为您比较两个永远不会相等的 指针(并且只有指针本身)。要比较 C 风格的以 null 结尾的字符串,请使用std::strcmp。您应该始终首先检查argc,以确保存在相应的argv元素。 -
@Someprogrammerdude 当然,但这不是重点,它只是说明性的。我会在问题中解决它
-
目前还不清楚是什么问题。您直接进入了您尝试的内容,而没有解释实际问题。如果没有这些,这些尝试就会显得断章取义。请提供minimal reproducible example,因为不清楚
TxtTerminal或BdTerminal是什么,以及为什么当前(建议的)解决方案不适合您。 -
关于“......派生类不能直接初始化基类成员”和“派生类不能直接初始化基类成员”虽然这是真的,你可以 在派生类构造函数初始化列表中调用合适的基类构造函数:
Derived() : Base() {}(当然也可以传递参数)。如果您只想“重用”基类构造函数,那么“使用”它:class Derived : public Base { public: using Base::Base; ... }; -
在我看来,您唯一的问题是提供不同的常量以在 Base/Derived 类中使用,但这很容易通过为这些常量使用虚拟 getter 来解决方法。
标签: c++ static runtime constants dispatch