hsn*_*hsn 1 c++ polymorphism makefile g++
我正在实现集合层次结构,在这个项目中,我需要一些没有要实现的功能的抽象类,因此为这些类创建 .cpp 文件似乎是多余的。我有一个可以与 .cpp 文件一起正常工作的 Makefile,但在这种情况下出现了一些问题。
包含抽象类的文件(每个函数都是抽象的):
Run Code Online (Sandbox Code Playgroud)-collection.h -set.h -list.h -queue.h这些文件包含具体功能:
Run Code Online (Sandbox Code Playgroud)-hashSet.h -hashSet.cpp -arrayList.h -arrayList.cpp -linkedList.h -linkedList.cpp -iterator.h -iterator.cpp
我的 Makefile 如下
obj = main.o collection.o set.o list.o queue.o hashSet.o arrayList.o iterator.o
output : $(obj)
g++ -g -Wno-deprecated -std=c++11 -ansi -pedantic -Wall $(obj) -o output
main.o : main.cpp
g++ -g -Wno-deprecated -std=c++11 -c main.cpp
%.o : %.cpp %.h
g++ -g -Wno-deprecated -std=c++11 -c $<
clean :
rm *.o output
Run Code Online (Sandbox Code Playgroud)
当前错误:
make: *** No rule to make target 'collection.o', needed by 'output'. Stop.
Run Code Online (Sandbox Code Playgroud)
你能帮我重新设计Makefile吗?
如您所知,C++ 中头文件的用途是#include由预处理器在预处理.cpp文件时进行 -ed,以便它只是成为
编译器在编译该文件时使用的源代码的一部分.cpp。
因此,头文件header.h永远不会单独编译,也不会header.o
生成相应的目标文件。header.h是#include-ed 的,比如说,source.cpp;source.cpp被编译,包括 的内容header.h,生成的目标文件是source.o.
source.o显然取决于source.cpp:每当source.cpp更改时,您都需要重新编译它以生成新的source.o. 但因为source.cpp包含header.h,取决于:因此每当source.o更改时,您再次需要重新编译以生成新的.header.hheader.hsource.cppsource.o
这些是您需要在 makefile 中回答的问题:
source.o?source.o当不是最新的(即不存在或比它所依赖的某些文件更旧)时需要执行的操作。在 Make-speak 中, X所依赖的文件称为X的先决条件,而必须执行以使X保持最新的操作是X的配方。
所以你的 makefile 需要这样说:
source.o依赖于取决于source.cppsource.o依赖于取决于header.hsource.o不是最新的时候,source.cpp必须编译生成source.o就目前而言,仅此而已header.h。
这是一个具体的说明,类似于您的类层次结构项目及其仅标头的抽象基类:-
形状.h
#ifndef SHAPE_H
#define SHAPE_H
struct shape {
virtual ~shape() = default;
virtual double area() const = 0;
};
#endif
Run Code Online (Sandbox Code Playgroud)
矩形.h
#ifndef RECTANGLE_H
#define RECTANGLE_H
#include <shape.h>
struct rectangle : shape {
rectangle(double length, double width);
~rectangle() override = default;
double area() const override;
private:
double _length;
double _width;
};
#endif
Run Code Online (Sandbox Code Playgroud)
三角形.h
#ifndef TRIANGLE_H
#define TRIANGLE_H
#include <shape.h>
struct triangle : shape {
triangle(double side1, double side2, double side3);
~triangle() override = default;
double area() const override;
private:
double _side1;
double _side2;
double _side3;
};
#endif
Run Code Online (Sandbox Code Playgroud)
矩形.cpp
#include "rectangle.h"
rectangle::rectangle(double length, double width)
: _length(length),_width(width){}
double rectangle::area() const {
return _length * _width;
}
Run Code Online (Sandbox Code Playgroud)
三角形.cpp
#include "triangle.h"
#include <cmath>
triangle::triangle(double side1, double side2, double side3)
: _side1(side1),_side2(side2),_side3(side3){}
double triangle::area() const {
double halfperim = (_side1 + _side2 + _side3) / 2;
double area2ed = halfperim *
(halfperim - _side1) * (halfperim - _side2) * (halfperim - _side3);
return std::sqrt(area2ed);
}
Run Code Online (Sandbox Code Playgroud)
主程序
#include <shape.h>
#include <triangle.h>
#include <rectangle.h>
#include <memory>
#include <iostream>
int main()
{
std::unique_ptr<shape> s{new rectangle{2,3}};
std::cout << "Rectangular shape's area is " << s->area() << std::endl;
s.reset(new triangle{3,4,5});
std::cout << "Triangular shape's area is " << s->area() << std::endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
生成文件 (1)
# Builds program `prog`
.PHONY: clean # `clean` is a phony target, not a real file
prog: main.o rectangle.o triangle.o # Prerequisites of `prog`
prog: # This is how to make `prog` up-to-date
g++ -o $@ $^ # Link all the prerequisites (`$^`), output the target (`$@`)
main.o: main.cpp shape.h rectangle.h triangle.h # Prerequisites of `main.o`
rectangle.o: rectangle.cpp rectangle.h shape.h # Prerequisites of `rectangle.o`
triangle.o: triangle.cpp triangle.h shape.h # Prerequisites of `triangle.o`
%.o: # This is how to make any `*.o` file up-to-date
g++ -c -o $@ $< # Compile the first prerequisite (`$<`), output the target
clean:
rm -f prog main.o rectangle.o triangle.o
Run Code Online (Sandbox Code Playgroud)
Makefile以不切实际的风格编写,以尽量减少干扰,并强调指定目标的先决条件和指定使其保持最新的操作之间的区别。但它是正确的并且第一次运行如下:
$ make
g++ -c -o main.o main.cpp # Compile the first prerequisite (`main.cpp`), output the target
g++ -c -o rectangle.o rectangle.cpp # Compile the first prerequisite (`rectangle.cpp`), output the target
g++ -c -o triangle.o triangle.cpp # Compile the first prerequisite (`triangle.cpp`), output the target
g++ -o prog main.o rectangle.o triangle.o # Link all the prerequisites (`main.o rectangle.o triangle.o`), output the target (`prog`)
Run Code Online (Sandbox Code Playgroud)
之后prog运行如下:
$ ./prog
Rectangular shape's area is 6
Triangular shape's area is 6
Run Code Online (Sandbox Code Playgroud)
如果修改的话就会triangle.cpp过时。我们可以使用 shell 命令伪造修改:triangle.oprogtouch
$ touch triangle.cpp
$ make
g++ -c -o triangle.o triangle.cpp # Compile the first prerequisite (`triangle.cpp`), output the target
g++ -o prog main.o rectangle.o triangle.o # Link all the prerequisites (`main.o rectangle.o triangle.o`), output the target (`prog`)
Run Code Online (Sandbox Code Playgroud)
如果修改rectangle.hthen rectangle.o,main.o和prog将变得过时:
$ touch rectangle.h
$ make
g++ -c -o main.o main.cpp # Compile the first prerequisite (`main.cpp`), output the target
g++ -c -o rectangle.o rectangle.cpp # Compile the first prerequisite (`rectangle.cpp`), output the target
g++ -o prog main.o rectangle.o triangle.o # Link all the prerequisites (`main.o rectangle.o triangle.o`), output the target (`prog`)
Run Code Online (Sandbox Code Playgroud)
如果您修改shape.h(抽象基类),那么所有目标文件,加上prog,都将变得过时:
$ touch shape.h
$ make
g++ -c -o main.o main.cpp # Compile the first prerequisite (`main.cpp`), output the target
g++ -c -o rectangle.o rectangle.cpp # Compile the first prerequisite (`rectangle.cpp`), output the target
g++ -c -o triangle.o triangle.cpp # Compile the first prerequisite (`triangle.cpp`), output the target
g++ -o prog main.o rectangle.o triangle.o # Link all the prerequisites (`main.o rectangle.o triangle.o`), output the target (`prog`)
Run Code Online (Sandbox Code Playgroud)
如果Makefile以稍微专业一点的风格编写,它看起来像:
生成文件 (2)
SRCS := main.cpp rectangle.cpp triangle.cpp
OBJS := $(SRCS:.cpp=.o)
.PHONY: all clean
all: prog
prog: $(OBJS)
$(CXX) -o $@ $^
main.o: rectangle.h triangle.h shape.h
rectangle.o: rectangle.h shape.h
triangle.o: triangle.h shape.h
clean:
$(RM) prog $(OBJS)
Run Code Online (Sandbox Code Playgroud)
您可以在手册中研究其功能1
特别注意与Makefile(1) 的两个区别:-
1)通常将指定目标的先决条件与指定其配方结合起来。所以:
prog: $(OBJS)
$(CXX) -o $@ $^
Run Code Online (Sandbox Code Playgroud)
只是一种更短的写法:
prog: $(OBJS)
prog:
$(CXX) -o $@ $^
Run Code Online (Sandbox Code Playgroud)
或者确实:
prog: main.o
prog: rectangle.o
prog: triangle.o
$(CXX) -o $@ $^
Run Code Online (Sandbox Code Playgroud)
make将所有先决条件合并prog到一个列表中,如果目标相对于其中任何一个已过时,则执行配方。
2)制作文件的方法*.o已经消失,但是makefile仍然有效!
$ make clean
rm -f prog main.o rectangle.o triangle.o
$ make
g++ -c -o main.o main.cpp
g++ -c -o rectangle.o rectangle.cpp
g++ -c -o triangle.o triangle.cpp
g++ -o prog main.o rectangle.o triangle.o
Run Code Online (Sandbox Code Playgroud)
这是因为它make有一系列内置规则,而其中一个内置规则是file.o从制作的默认配方file.cpp。默认配方是:
%.o: %.cpp:
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $@ $<
Run Code Online (Sandbox Code Playgroud)
因此,我们不需要告诉make它,例如rectangle.o依赖项或告诉它如果该依赖项过时了rectangle.cpp
该怎么办。rectangle.o如果它需要rectangle.o最新并找到rectangle.cpp,那么内置规则会告诉它编译rectangle.cpp并输出rectangle.o。
但make没有内置规则告诉它rectangle.o依赖于rectangle.h
或main.o依赖于shape.h或依赖于triangle.h。这种可能的依赖关系有无限多种,因为目标文件的名称与编译源文件以生成该目标文件时可能包含的头文件的名称之间根本不存在系统关系。
因此,目标文件对头文件的依赖关系必须在 makefile 中阐明:
main.o: rectangle.h triangle.h shape.h
rectangle.o: rectangle.h shape.h
triangle.o: triangle.h shape.h
Run Code Online (Sandbox Code Playgroud)
现在,当我们的项目非常简单(例如prog. 但在现实项目中这是不切实际的。可能有数百个源文件和数百个头文件,并且一个源文件可能会递归地包含头文件中的头文件……通常,当我们需要编写 makefile 时,解开这些递归文件是不现实的。
然而,编译器(或者严格来说,预处理器)解开它们并不是不现实的:它在预处理源文件时必须准确地做到这一点。
因此,在使用 GNU Make 和 GCC 时,处理头文件依赖性的正常方法是利用 GCC 预处理器的一个功能
来解决这个问题。使用这个功能再次重写Makefile
,以更专业的风格,它将是:
生成文件 (3)
SRCS := main.cpp rectangle.cpp triangle.cpp
OBJS := $(SRCS:.cpp=.o)
DEPS := $(SRCS:.cpp=.d)
.PHONY: all clean
all: prog
prog: $(OBJS)
$(CXX) -o $@ $^
%.o: %.cpp
$(CXX) -c -MMD -o $@ $<
clean:
$(RM) prog $(OBJS) $(DEPS)
-include $(DEPS)
Run Code Online (Sandbox Code Playgroud)
您在这里看到,我们以模式规则的形式带回了制作file.ofrom
的
配方我们的模式规则:file.cpp
%.o: %.cpp
$(CXX) -c -MMD -o $@ $<
Run Code Online (Sandbox Code Playgroud)
调用 C++ 编译器($(CXX))进行编译file.cpp和输出file.o,并将预处理器选项传递给它-MMD。
该选项告诉预处理器写入一个附加的输出文件,file.d如果目标文件是file.o,则调用该输出文件,并且file.d将是一个makefilefile.o ,表达预处理器通过解析发现的所有先决条件file.cpp(不包括系统头文件)。
我们来看一下:
$ make clean
rm -f prog main.o rectangle.o triangle.o main.d rectangle.d triangle.d
$ make
g++ -c -MMD -o main.o main.cpp
g++ -c -MMD -o rectangle.o rectangle.cpp
g++ -c -MMD -o triangle.o triangle.cpp
g++ -o prog main.o rectangle.o triangle.o
$ cat main.d
main.o: main.cpp shape.h triangle.h rectangle.h
$ cat rectangle.d
rectangle.o: rectangle.cpp rectangle.h shape.h
$ cat triangle.d
triangle.o: triangle.cpp triangle.h shape.h
Run Code Online (Sandbox Code Playgroud)
如您所见,file.d是一个迷你 makefile,它指定了file.o.
DEPS := $(SRCS:.cpp=.d)
Run Code Online (Sandbox Code Playgroud)
列入$(DEPS)名单main.d rectangle.d triangle.d。和:
-include $(DEPS)
Run Code Online (Sandbox Code Playgroud)
包括 (3) 中的所有迷你 makefile Makefile。所以Makefile(3)等价于:
生成文件 (4)
SRCS := main.cpp rectangle.cpp triangle.cpp
OBJS := $(SRCS:.cpp=.o)
DEPS := $(SRCS:.cpp=.d)
.PHONY: all clean
all: prog
prog: $(OBJS)
$(CXX) -o $@ $^
%.o: %.cpp
$(CXX) -c -MMD -o $@ $<
clean:
$(RM) prog $(OBJS) $(DEPS)
main.o: main.cpp shape.h triangle.h rectangle.h
rectangle.o: rectangle.cpp rectangle.h shape.h
triangle.o: triangle.cpp triangle.h shape.h
Run Code Online (Sandbox Code Playgroud)
这种让预处理器找出头文件依赖项的技术通常称为自动依赖项生成,这些头文件依赖项太复杂而无法通过脑力来找出,这是处理您所询问的问题的专业方法。
您可能已经注意到它只有一个障碍。这些.d文件是处理器在make运行模式配方时创建的%.o: %.cpp。而且它们必须被include插入Makefile。make但由于它们在你第一次跑步之前不会存在,因此当你第一次跑步include时尝试它们注定会失败。一个先有鸡还是先有蛋的问题。make
解决这个问题的方法就是忽略include $(DEPS)
if$(DEPS)还不存在的失败,这就是我们写的原因:
-include $(DEPS)
Run Code Online (Sandbox Code Playgroud)
而不仅仅是:
include $(DEPS)
Run Code Online (Sandbox Code Playgroud)
-在 makefile 中添加命令前缀表示make忽略失败。
您可以通过阅读自动依赖项生成来更深入地了解自动依赖项生成
| 归档时间: |
|
| 查看次数: |
2240 次 |
| 最近记录: |