About
RSS

Bit Focus


Makefile 与 C 语系头文件依赖自动解析

h1. 如何获得源文件依赖的头文件?

这一部分内容与 Makefile 毫无关系, 它由编译器完成.

以 g++ 为例, 可以尝试使用以下方法输出 .cpp 文件依赖的头文件

g++ -M [your cpp file]

乱糟糟的, 不是吗? 这是因为 g++ 把依赖的库文件也给列出来了. 忽略库文件, 则使用这样的命令

g++ -MM [your cpp file]

显然, 这些东西扔到标准输出是毫无用处的, 可以对其进行重定向

g++ -MM [your cpp file] > [some file]

但是这还是有问题, 只把依赖关系放进文件当然不行, 编译源文件的指令还没弄出来呢. 下面就得另外想办法了.

h1. 怎么添加编译指令并运行它们?

添加指令本质上就是追加文件内容, 方法有难有简单, 最简单的无非是写个脚本 echo 并追加重定向到文件中. 在这篇粗陋的文章里就用这招了. 好, 写个 Makefile 脚本

[target.o]:[your cpp file]
....g++ -MM $< > Makefile.tmp
....echo "....g++  $< -c" >> Makefile.tmp
....make -f Makefile.tmp

这里的四个点号 (....) 表示一个制表符, 嗯, 是的, Makefile 只能用万恶的制表符来缩进, 而且还必须缩进; Makefile.tmp 一定不要是当前的 Makefile, 要不然就出现文件覆盖的悲剧了...

结果就是, 一旦 make 到这个目标, 它会生成一串依赖关系和构建方法到另一个 Makefile, 然后把编译这等脏活累活都甩给那个倒霉的家伙.

h1. 怎么转移目标?

如果上述目标仍然写成 xxx.o : xxx.cpp, 那么如果仅仅是依赖的头文件被修改了, 这个目标仍然不会被 make 到. 一个很挫的方法是, 不要把目标定为 xxx.o 文件, 相反, 弄成一个永不会生成的目标比较好, 比如叫 xxx.d 这个名字.

下面上一个完整的例子. 这个例子中有个叫做 DEP 的 Makefile 变量没有被声明过, 嗯, 这将是你的事情. 请将所有需要编译并连接的 cpp 源文件列个表, 然后修改它们的后缀变成 d, 那么 DEP 的值就是这个表.

Code Snippet 0-0

CXX=g++
CXXFLAGS=-Wall -pg
MKTMP=Makefile.tmp

all:object

object:$(DEP)
....$(CXX) $(CXXFLAGS) *.o -o $@

%.d:%.cpp
....$(CXX) -MM $< > $(MKTMP)
....echo "....$(CXX) $< $(CXXFLAGS) -c" >> $(MKTMP)
....make -f $(MKTMP)

clean:
....rm -f *.o
....rm -f test.out
....rm -f $(MKTMP)

再次提醒, 制表符出没注意. (可利用编辑器的全文替换, 4 点换成 1 制表符)

假如现在目录下有 main.cpp, class0.cpp, class1.cpp, 那么定义

DEP=main.d class0.d class1.d

将这句话插进上述源码中的开头位置然后 make 即可.

h1. 怎么收拾掉乱七八糟的输出?

接脏活的临时工经常会反馈出一些无聊的信息, 比如进入这个离开那个的, 如果希望保持控制台的清洁, 那么把不想看的都扔进垃圾桶吧

make > /dev/null

不要担心看不到 warning 和 error, 它们是通过 stderr 输出的, 而上述重定向只会干扰 stdout.

Permanent Link: /p/63 Load full text

Post tags:

 C
 C++
 Makefile

编译期数值计算程序设计思路

阅读这篇文章需要了解基本的 C++ 模板语法规则, 以及模板的泛化, 偏特化, 当然还包括完全特化这些基本概念. 这篇文章将给出基于 C++ 模板的 Metaprogramming 程序设计中需要注意的事项以及渐进式设计思路.

0. 基础

这一段介绍 Metaprogramming 基础, 包括模板模式匹配规则, 使用模板实现循环或分支的基本方法. 对 Metaprogramming 已经有了解的同学可以跳过这一段.

本质上来说, 现代计算机的核心任务是数值计算, 周边任务是 I/O. 现代几个主流的编程语言, 如 C/C++/Java, 它们的语法组织方式与现代计算机的主流体系结构是 "平行" 的, 即, 语句的先后顺序表示它们的逻辑顺序, 而这种顺序与人的一贯思维顺序又是相同的 (在计算机中表达式的计算顺序会与书写顺序稍有不同, 但却很符合人类的思路); 可以说这是什么样的爹生什么样的崽, 什么样的人造什么样的语言和计算机.

然而, 在 C++ 模板世界中, 这个规则被打破了. 从分类上来说, Metaprogramming 更类似于函数式编程, 而不是传统的 C++ 程序设计. 在这个异世界中, 程序员不被允许使用循环或者分支语句, 包括函数返回语句都不允许: 在函数式编程中起码还有函数返回, 而在 Metaprogramming 中, 由于一切函数都没有被调用 (还在编译呢), 因此一切的值都谈不上返回, 它们只是存在于模板符号表中, 一旦编译结束便烟消云散.

当然, 没有循环, 分支这些编程语言中的基本元素, 并不意味着程序员只被允许设计顺序程序. 只不过, 在这里, 一切分流动作都被转化为了另一个语言特性: 模式匹配. (这一点与函数式编程非常相似)

下面一段代码展示了模式匹配的威力:

Code Snippet 0-0

#include <iostream>

template <unsigned I>
struct limit_to_2
{
    static unsigned const R = 2;
};

template <>
struct limit_to_2<1>
{
    static unsigned const R = 1;
};

template <>
struct limit_to_2<0>
{
    static unsigned const R = 0;
};

int main(void)
{
    std::cout << limit_to_2<0>::R << std::endl;
    std::cout << limit_to_2<1>::R << std::endl;
    std::cout << limit_to_2<2>::R << std::endl;
    std::cout << limit_to_2<3>::R << std::endl;
    return 0;
}

模板 limit_to_2 为传入的整数设置了一个上限, 当传入模板的参数为 0 或者 1 时内部定义的 R 值与参数相同, 否则将 R 设置为 2. 这看起来很像是 switch-case 语句, 0 和 1 分别是两个 case, 而未特化的模板则是 default.

但是, 这样做有一些不方便, 如果所设的上限非常高, 那岂不是要为低于限制的情况写很多特化? 所以引入分支是非常必要的. 下面这个例子展示了如何利用模式匹配来实现编译期分支:

Permanent Link: /p/50 Load full text

Post tags:

 Template
 Metaprogramming
 C++

泛型编程中的策略类

在编译期想要组合策略, 除开人肉内联不算, 有很多方式. 比如弄很多很多的 bool 参数

template <bool AllowDuplicate, bool SortElements, bool CheckOutOfRange>
struct just_another_container;

这看起来就是一个参数灾难, 如果某个地方出点小的模板编译错误, 哼哼~

当然, 另一种简单的替代方案就是把所有的 bool 整合在一起, 形成一个位段

template <unsigned Policy>
struct just_another_container;

这种情况实际上缺点更多, 比如维护位域会很麻烦, 有可能在这个声明之前有这些枚举

enum {
    ALLOW_DUP_MASK = 1,
    SORT_ELE_MASK = 2,
    CHECK_OUT_OF_RANGE_MASK = 4,
};

考虑这个容器有个 insert 函数, 它很可能需要考虑是否允许重复元素

template <unsigned AllowDuplicate>
void insert(element_type e);

而令人郁闷的是, 在 C++ 中不允许给函数偏特化, 也就是说这样写

template <>
void insert<0>(element_type e);
template <>
void insert<ALLOW_DUP_MASK>(element_type e);

是无法编译的! 要绕过这道坎, 得把 insert 函数放入一个模板结构体

template <unsigned AllowDuplicate> struct insert_s;
template <>
struct insert_s<ALLOW_DUP_MASK>
{
    static void insert(just_a_container& container, element_type& e);
};

这样做代价高昂, 首先, insert 被当作孤儿一样的抛弃, 那么类中的任何非 public 成员都不再对它开放, 当然, 为了这么个简单的小玩意儿声明 friend 也是可行的, 但是多了不觉得烦吗?

另外, 在调用策略行为进行特化时一般只针对特定的位, 那么将该策略位从组合策略中分离出来, 代码中将充斥着这样的运算

Policy & ALLOW_DUP_MASK
Policy & CHECK_OUT_OF_RANGE_MASK

比如

void another_member_function()
{
    element_type ele;
    // wanna call insert function of policy = Policy
    insert_s<*Policy & ALLOW_DUP_MASK*>::insert(*this, ele);
    // ...
}

这样做非常麻烦, 如果某个地方漏了 & 或者跟错误的掩码进行了运算, 哼哼~

不过, 泛型程序设计有自己的方法, 那就是, 多继承!

不要吓到了, 是的, 在 C++ 远古时代, 多继承被认定为獠牙猛兽. 不过那都是跟 C++ 运行时多态相关的. 步入泛型新纪元的 C++ 又迎来了多继承第二春, 它可以显著改善上述问题.

将上面的声明编程

template <*typename Policy*>
struct just_another_container;

然后加上这些结构体声明

struct policy_base {};
struct allow_dup : public policy_base {};
struct sort_ele : public policy_base {};
struct check_out_of_range : public policy_base {};

组合策略, 只需要声明一个策略类型继承想要的策略, 并把它传入容器就能达到目的, 比如

Permanent Link: /p/36 Load full text

Post tags:

 Generic Programming
 C++
 Template

0 1 2 3 Page 4


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site