类与其基类之间有哪些可检测到的差异?
给定以下模板:
template <typename T>
class wrapper : public T {};
Foo
类型的对象和 wrapper
类型的对象之间在接口或行为上有哪些明显的差异?
我已经知道一个:
wrapper
只有一个空构造函数、复制构造函数和赋值运算符(并且仅当这些操作在Foo
上有效时才具有这些操作符) )。通过在wrapper
中使用一组将值传递给 T 构造函数的模板化构造函数,可以减轻这种差异。
但我不确定可能存在哪些其他可检测到的差异,或者是否有隐藏它们的方法。
(编辑)具体示例
有些人似乎在询问这个问题的一些背景,所以这里是我的情况的(稍微简化的)解释。
我经常编写具有可调整值以调整系统的精确性能和操作的代码。我希望有一种简单(低代码开销)的方式通过配置文件或用户界面公开这些值。我目前正在编写一个库来允许我做到这一点。预期的设计允许像这样使用:
class ComplexDataProcessor {
hotvar<int> epochs;
hotvar<double> learning_rate;
public:
ComplexDataProcessor():
epochs("Epochs", 50),
learning_rate("LearningRate", 0.01)
{}
void process_some_data(const Data& data) {
int n = *epochs;
double alpha = *learning_rate;
for (int i = 0; i < n; ++i) {
// learn some things from the data, with learning rate alpha
}
}
};
void two_learners(const DataSource& source) {
hotobject<ComplexDataProcessor> a("FastLearner");
hotobject<ComplexDataProcessor> b("SlowLearner");
while (source.has_data()) {
a.process_some_data(source.row());
b.process_some_data(source.row());
source.next_row();
}
}
运行时,这将设置或读取以下配置值:
FastLearner.Epochs
FastLearner.LearningRate
SlowLearner.Epochs
SlowLearner.LearningRate
这是编写的代码(碰巧我的用例甚至不是机器学习),但它显示了一些重要的信息设计的各个方面。可调整的值均已命名,并且可以组织成层次结构。值可以通过多种方法进行分组,但在上面的示例中,我只展示了一种方法:将对象包装在 hotobject
类中。实际上,hotobject
包装器有一个相当简单的工作 - 它必须将对象/组名称推送到线程本地上下文堆栈上,然后允许 T
要构造的对象(此时构造了 hotvar
值并检查上下文堆栈以查看它们应位于哪个组),然后弹出上下文堆栈。
这是按如下方式完成的:
struct hotobject_stack_helper {
hotobject_stack_helper(const char* name) {
// push onto the thread-local context stack
}
};
template <typename T>
struct hotobject : private hotobject_stack_helper, public T {
hotobject(const char* name):
hotobject_stack_helper(name) {
// pop from the context stack
}
};
据我所知,这种情况下的构造顺序是非常明确定义的:
- 构造了
hotobject_stack_helper
(将名称推送到上下文堆栈上) T
被构造——包括构造每个T
的成员(hotvars)。hotobject
构造函数的主体被运行,这会弹出上下文堆栈。
所以,我有工作代码来做到这一点。然而,还有一个问题,那就是:使用这种结构可能会给自己带来什么问题。这个问题在很大程度上简化为我实际要问的问题:hotobject 的行为与 T 本身有何不同?
Given the following template:
template <typename T>
class wrapper : public T {};
What visible differences in interface or behaviour are there between an object of type Foo
and an object of type wrapper<Foo>
?
I'm already aware of one:
wrapper<Foo>
only has a nullary constructor, copy constructor and assignment operator (and it only has those if those operations are valid onFoo
). This difference may be mitigated by having a set of templated constructors inwrapper<T>
that pass values through to the T constructor.
But I'm not sure what other detectable differences there might be, or if there are ways of hiding them.
(Edit) Concrete Example
Some people seem to be asking for some context for this question, so here's a (somewhat simplified) explanation of my situation.
I frequently write code which has values which can be tuned to adjust the precise performance and operation of the system. I would like to have an easy (low code overhead) way of exposing such values through a config file or the user interface. I am currently writing a library to allow me to do this. The intended design allows usage something like this:
class ComplexDataProcessor {
hotvar<int> epochs;
hotvar<double> learning_rate;
public:
ComplexDataProcessor():
epochs("Epochs", 50),
learning_rate("LearningRate", 0.01)
{}
void process_some_data(const Data& data) {
int n = *epochs;
double alpha = *learning_rate;
for (int i = 0; i < n; ++i) {
// learn some things from the data, with learning rate alpha
}
}
};
void two_learners(const DataSource& source) {
hotobject<ComplexDataProcessor> a("FastLearner");
hotobject<ComplexDataProcessor> b("SlowLearner");
while (source.has_data()) {
a.process_some_data(source.row());
b.process_some_data(source.row());
source.next_row();
}
}
When run, this would set up or read the following configuration values:
FastLearner.Epochs
FastLearner.LearningRate
SlowLearner.Epochs
SlowLearner.LearningRate
This is made up code (as it happens my use case isn't even machine learning), but it shows a couple of important aspects of the design. Tweakable values are all named, and may be organised into a hierarchy. Values may be grouped by a couple of methods, but in the above example I just show one method: Wrapping an object in a hotobject<T>
class. In practice, the hotobject<T>
wrapper has a fairly simple job -- it has to push the object/group name onto a thread-local context stack, then allow the T
object to be constructed (at which point the hotvar<T>
values are constructed and check the context stack to see what group they should be in), then pop the context stack.
This is done as follows:
struct hotobject_stack_helper {
hotobject_stack_helper(const char* name) {
// push onto the thread-local context stack
}
};
template <typename T>
struct hotobject : private hotobject_stack_helper, public T {
hotobject(const char* name):
hotobject_stack_helper(name) {
// pop from the context stack
}
};
As far as I can tell, construction order in this scenario is quite well-defined:
hotobject_stack_helper
is constructed (pushing the name onto the context stack)T
is constructed -- including constructing each ofT
's members (the hotvars)- The body of the
hotobject<T>
constructor is run, which pops the context stack.
So, I have working code to do this. There is however a question remaining, which is: What problems might I cause for myself further down the line by using this structure. That question largely reduces to the question that I'm actually asking: How will hotobject behave differently from T itself?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
奇怪的问题,因为您应该询问有关您的具体用法的问题(“我想做什么,这对我有什么帮助或伤害”),但我想一般来说:
wrapper
不是T
,因此:T
一样构造。 (正如您所注意到的。)T
一样进行转换。T
有权访问的私有资源的访问权限。我确信还有更多,但前两个涵盖了相当多的内容。
Strange question, since you should be asking questions about your specific usage ("what do I want to do, and how does this help me or hurt me"), but I guess in general:
wrapper<T>
is not aT
, so:T
. (As you note.)T
.T
has access to.And I'm sure there are more, but the first two cover quite a bit.
假设你有:
现在你可以说:
但是,你不能说:
也就是说,即使它们的类型参数可能具有继承关系,但通过特化模板生成的两个类型不具有任何继承关系。
Suppose you have:
Now you can say:
However, you cannot say:
That is, even though their type parameters may have an inheritance relationship, two types produced by specialising a template do not have any inheritance relationship.
对对象的引用可转换(给予访问)对基类子对象的引用。有语法糖可以调用隐式转换,允许您将对象视为基类的实例,但这确实是正在发生的事情。不多也不少。
因此,差异根本不难察觉。它们(几乎)是完全不同的东西。 “is-a”关系和“has-a”关系之间的区别在于指定成员名称。
至于隐藏基类,我认为您无意中回答了您自己的问题。通过指定
private
(或为class
省略public
)来使用私有继承,并且这些转换不会在类本身之外发生,并且不会发生。其他班级将能够知道基地甚至存在。A reference to an object is convertible (given access) to a reference to a base class subobject. There is syntactic sugar to invoke implicit conversions allowing you to treat the object as an instance of the base, but that's really what's going on. No more, no less.
So, the difference is not hard to detect at all. They are (almost) completely different things. The difference between an "is-a" relationship and a "has-a" relationship is specifying a member name.
As for hiding the base class, I think you inadvertently answered your own question. Use private inheritance by specifying
private
(or omittingpublic
for aclass
), and those conversions won't happen outside the class itself, and no other class will be able to tell that a base even exists.如果您继承的类有自己的成员变量(或至少一个),那么
If your inherited class has its own member variables (or at least one), then