C语言编程中,常常需要返回一些句柄,而这些句柄就是一些结构体,此时我指望外部的人调用我们的函数,传入指定的句柄进行操作,这样外部人就不会破坏句柄结构体中的信息,还可以完成指定操作的目的。
方法就是讲结构体的定义在.c文件中
1 xxx.c 2 3 struct xxxx{ 4 xxxx; 5 xxxxx; 6 };
然后在.h文件中
1 xxx.h 2 3 typedef struct xxxx abcd;
这样外部人员可以指定声明结构体或者结构体的指针,而无法改动结构体里的数据。
类的定义
访问限定和信息隐藏
信息和实现的隐藏可以防止类的内部表示被直接访问。
C++通过限定成员的访问权限来设置边界,实现信息隐藏。三个关键字:public、private、protected被称为访问界定符。
一个访问界定符的作用会持续到下一个访问界定符出现之前或者类定义结束。
如果没有指定访问权限,struct成员的访问权限默认为 public。
public成员在程序的任何函数或者类中都可以访问。public用于声明类接口中的成员。
private成员只能由类自己的成员函数或者友元访问。
protected成员的访问权限介于public和private之间,主要用于继承中。可以有类自己的成员函数、友元、以及派生类成员访问。
struct ch_stack{ public://类的接口 void clear(); void push(char c); char pop(); char top(); bool empty(); bool full(); private: char str[50]; int tp; };
增加了成员访问限制的类有以下好处:1、加强了类内部的安全性和一致性。
2、降低了客户程序员操纵该类型的复杂程度。
3、类的设计者改变这个类的内部工作方式时客户程序不会受影响。
C语言编程中,常常需要返回一些句柄,而这些句柄就是一些结构体,此时我指望外部的人调用我们的函数,传入指定的句柄进行操作,这样外部人就不会破坏句柄结构体中的信息,还可以完成指定操作的目的。
方法就是讲结构体的定义在.c文件中
1 xxx.c 2 3 struct xxxx{ 4 xxxx; 5 xxxxx; 6 };然后在.h文件中
1 xxx.h 2 3 typedef struct xxxx abcd;这样外部人员可以指定声明结构体或者结构体的指针,而无法改动结构体里的数据。
转载于:https://www.cnblogs.com/cxjchen/archive/2013/05/15/3079650.html
在caffe中,在网络训练和网络使用的过程中,都会涉及到加载网络结构的问题,即caffe中定义的prototxt文件。但该文件包括了几乎所有的网络信息,能否做到隐藏该文件呢?
能够想到的有两种方式,
1、加密该文件,使用时再解密该文件
2、将网络结构使用c++代码实现
其中方式2没有做太多的研究,应该比较麻烦。
那么加密解密的方式如何实现呢?采用合适的加密算法,解密后变成string,将string解析,变成网络结构的参数,进行处理。
这种方式,会保存有加密文件,但并不想包含这样一个文件,而且需要设计加密算法,这个如何设计呢?
于是,我想到了另外一种方式,把网络结构的定义,用C++代码一行一行的实现,即用C++的方式写一个prototxt文件,将该文件保存为临时文件,使用该临时文件后,马上删除该文件。
当然这种方式仍然有一定的风险,就该临时文件的存在,那么能不能把加密的方式和代码实现网络结构的方式相结合呢?其实用代码实现网络结构只是替代了加密的过程,用代码生成网络结构后,不生成prototxt文件,而是在内存中以string的方式存在,把此string当做参数传入即可
想要隐藏网络结构,对于训练阶段和测试阶段还不太一样,测试阶段只有一个deploy.prototxt文件,而且针对测试阶段,前面是自己写了c++的代码读取deploy.prototxt,加载模型的。改改这里就好了。
但训练阶段,是调用的caffe的可执行文件,那么如果要更改传参方式,就需要修改caffe的源码,主要是caffe/tools/caffe.cpp文件,修改其中读取train_val.prototxt和solver.prototxt文件的部分。
首先实现测试阶段的过程,其实在caffe源码中,读取deploy.prototxt文件也是通过解析该文件,得到NetParameters,那么通过解析string,得到NetParameters的方式,就也是可行的。
通过caffe源码一点点分析,prototxt文件首先是通过是传入net,在caffe/cpp_classification/classification中,有这样一行代码
-Cpp 代码
1
net_.reset(new Net<float>(model_file, TEST));
其中model_file就是是deploy.prototxt文件,通过构造函数,构建网络,Net类在caffe/src/caffe/net.cpp中,如下
-Cpp 代码
01
template <typename Dtype>
02
Net<Dtype>::Net(const string& param_file, Phase phase,
03
const int level, const vector<string>* stages) {
04
NetParameter param;
05
ReadNetParamsFromTextFileOrDie(param_file, ¶m);
06
// Set phase, stages and level
07
param.mutable_state()->set_phase(phase);
08
if (stages != NULL) {
09
for (int i = 0; i < stages->size(); i++) {
10
param.mutable_state()->add_stage((*stages)[i]);
11
}
12
}
13
param.mutable_state()->set_level(level);
14
Init(param);
15
}
这里首先构建NetParameter param,在通过
-Cpp 代码
1
ReadNetParamsFromTextFileOrDie(param_file, ¶m);
从deploy.prototxt中解析出来网络结构,把网络结构赋值给param。
再进入到
-Cpp 代码
1
ReadNetParamsFromTextFileOrDie()
函数中,看下是如何操作的,该函数在caffe/src/caffe/util/upgrade_proto.cpp中,如下:
-Cpp 代码
1
void ReadNetParamsFromTextFileOrDie(const string& param_file,
2
NetParameter* param) {
3
CHECK(ReadProtoFromTextFile(param_file, param))
4
<< "Failed to parse NetParameter file: " << param_file;
5
UpgradeNetAsNeeded(param_file, param);
6
}
在该函数中又调用
-Cpp 代码
1
ReadProtoFromTextFile()
从deploy.prototxt文件中读取数据,然后网络结构保存在param中。
再进入到ReadProtoFromTextFile()函数看下,其中的运行机制,该函数在caffe/src/caffe/util/io.cpp中,如下
-Cpp 代码
1
bool ReadProtoFromTextFile(const char* filename, Message* proto) {
2
int fd = open(filename, O_RDONLY);
3
CHECK_NE(fd, -1) << "File not found: " << filename;
4
FileInputStream* input = new FileInputStream(fd);
5
bool success = google::protobuf::TextFormat::Parse(input, proto);
6
delete input;
7
close(fd);
8
return success;
9
}z
在这里,首先是通过读文件的方式,将deploy.prototxt读出来,然后通过protobuf的parse解析出来即可。
如果需要需要自己实现一个从string中解析网络结构的功能,那么前面的大部分内容都可以照搬,最后再调用protobuf的TextFormat::ParseFromString即可。
前面分析清楚了,那么下面来试试如何修改caffe的源代码(在修改的时候,别忘了修改.h文件),首先是在io.cpp中需要增加一个函数(并在caffe/include/caffe/util/io.h中增加相应的声明),如下
-Cpp 代码
1
bool ReadProtoFromString(const string &str, Message* proto){
2
bool success = google::protobuf::TextFormat::ParseFromString(str, proto);
3
return success;
4
5
}
然后在upgrade_proto.cpp中增加ReadNetParamsFromStringOrDie(同时修改.h文件),如下
-Cpp 代码
1
void ReadNetParamsFromStringOrDie(const string& str, NetParameter* param){
2
3
const string output = "It's a string, not a file.";
4
CHECK(ReadProtoFromString(str, param))
5
<< "Failed to parse NetParameter from string" << output;
6
7
UpgradeNetAsNeeded(output,param);
8
}
这里新增了一个output字符串,主要是为了输出,之前的param_file是文件,而目前是字符串,不适合输出str。
另外这里面的函数UpgradeNetAsNeeded,主要是根据param来判断,output只是输出用,故用output也是可以的,不会对output做任何的解析。
之后再在Net.cpp中做相应的修改
-Cpp 代码
01
template <typename Dtype>
02
Net<Dtype>::Net(int usage, const string& str, Phase phase, const int level, const vector<string>* stages){
03
NetParameter param;
04
ReadNetParamsFromStringOrDie(str, ¶m);
05
//Set phase, stages and level
06
param.mutable_state()->set_phase(phase);
07
if (stages != NULL){
08
for (int i = 0; i < stages->size(); i++) {
09
param.mutable_state()->add_stage((*stages)[i]);
10
}
11
}
12
param.mutable_state()->set_level(level);
13
Init(param);
14
15
}
第一个参数int,并无实际意义,仅仅用作和前面的构造函数进行区分。
现在一切准备就绪后,就需要开始通过string构建网络结构。
其实用string构建网络很简单,只需要把deploy.prototxt中的字符串提取出来,用一个字符串表示即可,原本的回车换行符以"\n"替代即可,另外原始文件中的双引号可以用单引号替代
那么现在就完成了对测试网络的deploy.prototxt文件的隐藏,如果要对训练的网络结构进行隐藏,则需要修改其它的代码。
首先,明确一下,训练时用到的代码见caffe/tools/caffe.cpp中,编译后,会在caffe/build/中找到可执行文件caffe,因此可以用该可执行文件进行网络的训练。一般调用方式为
-Bash 代码
1
./build/caffe train solver.prototxt
这里只传入了solver.prototxt,该文件也是需要隐藏的(另外,在该文件中,给出了train_val.prototxt文件的位置,这里先介绍如何隐藏solver.prototxt文件,再看看如何隐藏train_val.prototxt文件)
在caffe.cpp文件中,使用solver.prototxt文件的代码如下:
-Cpp 代码
1
caffe::SolverParameter solver_param;
2
caffe::ReadSolverParamsFromTextFileOrDie(FLAGS_solver, &solver_param);
其中,FLAGS_solver表示solver.prototxt文件,可见,这里和前面隐藏deploy.prototxt文件是一致的。做相应的修改。ReadSolverParamsFromTextFileOrDie()函数在caffe/src/caffe/util/upgrade_proto.cpp中,如下:
-Cpp 代码
1
void ReadSolverParamsFromTextFileOrDie(const string& param_file,
2
SolverParameter* param) {
3
CHECK(ReadProtoFromTextFile(param_file, param))
4
<< "Failed to parse SolverParameter file: " << param_file;
5
UpgradeSolverAsNeeded(param_file, param);
6
}
这里同样还是调用ReadProtoFromTextFile,那么修改的时候,也是类似的,只需要再这里增加一个函数ReadSolverParamsFromStringOrDie,在这个函数里面调用的函数是和前面一样的,这里已经准备好了。如下:
-Cpp 代码
1
void ReadSolverParamsFromStringOrDie(const string& str, SolverParameter* param){
2
const string output = "It's a string, not a file.";
3
CHECK(ReadProtoFromString(str, param))
4
<< "Failed to parse NetParameter from string" << output;
5
UpgradeSolverAsNeeded(output,param);
6
}
再相应的修改.h文件即可。
在solver.prototxt文件中包含了train_val.prototxt文件的位置,从此地址中获取网络结构,那么就还需要隐藏该网络结构。在caffe.cpp中从solver.prototxt读取训练的参数信息后,保存到solver_param对象中,再用该对象构建solver对象,
-Cpp 代码
1
caffe::ReadSolverParamsFromTextFileOrDie(FLAGS_solver, &solver_param);
2
shared_ptr<caffe::Solver<float> >
3
solver(caffe::SolverRegistry<float>::CreateSolver(solver_param));
在构建solver对象的过程中,初始化了网络结构(包括训练和测试的网络结构)
-Cpp 代码
1
InitTrainNet();
2
InitTestNets();
先看如何修改训练的网络,改成从字符串中读取。初始训练的网络代码如下:
-Cpp 代码
01
template <typename Dtype>
02
void Solver<Dtype>::InitTrainNet() {
03
const int num_train_nets = param_.has_net() + param_.has_net_param() +
04
param_.has_train_net() + param_.has_train_net_param();
05
const string& field_names = "net, net_param, train_net, train_net_param";
06
CHECK_GE(num_train_nets, 1) << "SolverParameter must specify a train net "
07
<< "using one of these fields: " << field_names;
08
CHECK_LE(num_train_nets, 1) << "SolverParameter must not contain more than "
09
<< "one of these fields specifying a train_net: " << field_names;
10
NetParameter net_param;
11
if (param_.has_train_net_param()) {
12
LOG_IF(INFO, Caffe::root_solver())
13
<< "Creating training net specified in train_net_param.";
14
net_param.CopyFrom(param_.train_net_param());
15
} else if (param_.has_train_net()) {
16
LOG_IF(INFO, Caffe::root_solver())
17
<< "Creating training net from train_net file: " << param_.train_net();
18
ReadNetParamsFromTextFileOrDie(param_.train_net(), &net_param);
19
}
20
if (param_.has_net_param()) {
21
LOG_IF(INFO, Caffe::root_solver())
22
<< "Creating training net specified in net_param.";
23
net_param.CopyFrom(param_.net_param());
24
}
25
if (param_.has_net()) {
26
LOG_IF(INFO, Caffe::root_solver())
27
<< "Creating training net from net file: " << param_.net();
28
ReadNetParamsFromTextFileOrDie(param_.net(), &net_param);
29
}
30
// Set the correct NetState. We start with the solver defaults (lowest
31
// precedence); then, merge in any NetState specified by the net_param itself;
32
// finally, merge in any NetState specified by the train_state (highest
33
// precedence).
34
NetState net_state;
35
net_state.set_phase(TRAIN);
36
net_state.MergeFrom(net_param.state());
37
net_state.MergeFrom(param_.train_state());
38
net_param.mutable_state()->CopyFrom(net_state);
39
net_.reset(new Net<Dtype>(net_param));
40
}
这里会运行到if(param_.has_net())的条件里,在这里利用这个条件,但选择从string中读取网络结构。
首先在caffe/src/caffe/proto/caffe.proto文件中的SolverParameter中增加bool类型的变量表明是从字符串中读取网络结构:
如下:
-Cpp 代码
1
//parse net from string
2
optional bool net_from_str = 101 [default = false];<br>
并在solver.prototxt对应的字符串中增加对应的字段,令其为true。
那么在sovler.cpp中做一些修改
-Cpp 代码
01
if (param_.has_net()) {
02
LOG_IF(INFO, Caffe::root_solver())
03
<< "Creating training net from net file: " << param_.net();
04
if (param_.net_from_str()){
05
AppClsfyNet app_clsfy_net(301);
06
string str_net = app_clsfy_net.getTrainNetStr();
07
ReadNetParamsFromStringOrDie(str_net, &net_param);
08
std::cout<<str_net<<std::endl;
09
}else{
10
ReadNetParamsFromTextFileOrDie(param_.net(), &net_param);
11
}
12
}
需要在solver中先include自己写的获取train net的.h文件,并从中获取train net的字符串。那么到这里,train_net搞定。那么接下来看看如何初始化TestNet.
对于test net而言,也是一样的,对于其中的代码做修改如下:
-Cpp 代码
01
if (has_net_file) {
02
for (int i = 0; i < remaining_test_nets; ++i, ++test_net_id) {
03
sources[test_net_id] = "net file: " + param_.net();
04
if(param_.net_from_str()){
05
AppClsfyNet app_clsfy_net(301);
06
string str_net = app_clsfy_net.getTrainNetStr();
07
ReadNetParamsFromStringOrDie(str_net, &net_params[test_net_id]);
08
}else{
09
ReadNetParamsFromTextFileOrDie(param_.net(), &net_params[test_net_id]);
10
}
11
}
12
}
好了,到此为止,基本是搞定了,网络结构都可以隐藏了,不管是训练阶段还是测试阶段的网络结构都隐藏了。
其中部分示例代码来源于《JavaScript设计模式》一书
封装的主要目的就是将部分变量和函数隐藏起来,只让使用者看到想让他们看到的东西,而隐藏起来不想让他们看到的东西。这样做有以下好处:
不隐藏信息的类的定义:
function foo() {
this.a = 10;
}
foo.prototype.bar= function() {
this.a *= 2;
}
这时我们是可以通过foo.a来访问变量a的。
隐藏信息的类的定义:
function foo() {
var a = 10;
function bar() {
a *= 2;
return a;
}
return bar;
}
var baz = foo();
baz();//return 20
baz();//return 40
baz();//return 80
var blat = foo();
blat();//return 20
在这里我们并不能通过foo.a访问a,因为a并不是foo的属性,只是在该函数作用域内声明的一个局部变量。而当我们调用bar函数时,该函数并非运行在其调用时所在作用域,而是运行在定义bar函数的作用域处,即a所在作用域,故bar函数作为foo的子函数,可以访问其父函数中的变量,并且不同的实例化对象,各自有一个作用域和变量和子函数的副本,不同实例中的变量值无关。总结而言
所谓静态方法和属性,便是该类对象所共有的一个方法和属性,不随着实例化对象的增加而增加,所有该类对象公用这一个方法或属性。将函数和属性定义为静态的原因:
举个栗子:
var foo_object = (function foo() {
var numberOfFoo = 10;//私有静态属性
return function bar(a_value) {/*通过返回构造函数,foo_object被赋值为该构造函数。
每次实例化该类对象,只是产生该构造函数作用域和该作用域下定义的变量和函数*/
var a = 10;//私有非静态属性
a = 2 * a * a_value;
numberOfFoo++;
if (numberOfFoo > 50)
throw new Error('Only 50 instances of foo can be created');//通过静态变量控制类被实例化的次数。
}
})();
var obj1 = foo_object(1);//numberOfFoo=1 a=20
var obj2 = foo_object(3);//numberOfFoo=2 a=60
对于静态变量,很简单,你想让该类下的实例化对象公用该变量,就将该变量定义为静态变量。
对于静态函数,还要考虑一点,由上面的例子可见,若定义静态函数,则该函数将被定义在构造函数之外,那么该函数也就无法调用构造函数中的私有变量了。因此
只有当你不需要使用该函数访问非静态私有变量时,你才应该考虑将函数定义为静态函数。
即定义一个赋予初值的静态变量,为该变量只定义取值的函数,而不定义改变量值得函数。