亚信科技 软件开发-C++ 一面
1. 请介绍你在项目中的主要工作内容和技术方向
2. 你在 C++ 开发中使用的是哪个标准,具体使用过 C++11/14/17 的哪些新特性
答案:主要使用 C++17,部分代码保持 C++11 兼容。C++11 里常用的是智能指针、右值引用、移动语义、lambda、auto、范围 for、nullptr、线程库、原子操作、override 和 final。C++14 里用过泛型 lambda、make_unique。C++17 里用过结构化绑定、if constexpr、std::optional、std::variant、std::string_view 和文件系统相关接口。
在项目里,智能指针主要用来管理连接对象、策略对象和异步任务上下文;移动语义用于减少事件对象在队列投递过程中的拷贝;string_view 用在协议解析和规则匹配里,避免不必要的字符串复制。不过新特性不是越多越好,项目里还是会优先考虑可读性和团队维护成本。
代码:
#include <optional>
#include <string_view>
#include <iostream>
using namespace std;
optional<int> parseLevel(string_view s) {
if (s == "low") return 1;
if (s == "middle") return 2;
if (s == "high") return 3;
return nullopt;
}
int main() {
if (auto level = parseLevel("high"); level.has_value()) {
cout << *level << endl;
}
return 0;
}
3. 请说明 Lambda 表达式在处理问题时的优势
答案:lambda 最大的优势是可以在使用点附近定义短小逻辑,同时还能捕获上下文变量。在排序、自定义过滤、回调、异步任务、定时器、事件处理这些场景里,用 lambda 会比单独写一个函数或仿函数更简洁。比如在安全事件处理里,需要按风险等级排序、按终端 ID 过滤、或者把任务投递到线程池时,lambda 都比较自然。
另外 lambda 本质上是函数对象,编译器能看到具体类型,很多时候可以内联优化。但是它也不能滥用,如果 lambda 太长,或者捕获了很多外部变量,代码反而会变得难读。
代码:
#include <vector>
#include <algorithm>
using namespace std;
struct Event {
int hostId;
int risk;
};
int main() {
vector<Event> events = {{1, 80}, {2, 30}, {3, 95}};
sort(events.begin(), events.end(), [](const Event& a, const Event& b) {
return a.risk > b.risk;
});
int threshold = 60;
auto it = remove_if(events.begin(), events.end(), [threshold](const Event& e) {
return e.risk < threshold;
});
events.erase(it, events.end());
return 0;
}
4. 在使用 Lambda 表达式时遇到过哪些问题或坑
答案:最容易踩坑的是捕获方式,尤其是异步场景下捕获引用或者捕获 this。如果 lambda 被投递到线程池或者定时器里,外部对象可能已经析构了,这时候再访问引用或 this 就会产生悬空引用。同步场景里捕获引用问题不大,但异步场景更推荐捕获值,或者捕获 shared_ptr / weak_ptr 来控制生命周期。
另一个问题是 lambda 捕获大对象会产生拷贝成本。如果捕获的是大 vector、大字符串或者复杂对象,就要考虑是否用引用、移动捕获,或者只捕获必要字段。还有就是 lambda 太长时可读性会下降,这种情况我会把逻辑拆成普通函数或类方法。
代码:
#include <memory>
#include <iostream>
using namespace std;
class Session : public enable_shared_from_this<Session> {
public:
void asyncCheck() {
weak_ptr<Session> weakSelf = shared_from_this();
auto cb = [weakSelf]() {
if (auto self = weakSelf.lock()) {
cout << "session still alive\n";
}
};
cb();
}
};
5. 你了解哪些智能指针,具体使用过哪些
答案:常用智能指针主要是 unique_ptr、shared_ptr 和 weak_ptr。unique_ptr 表示独占所有权,不能拷贝,只能移动,适合资源归属明确的对象,比如策略解析器、文件句柄封装、buffer 对象。shared_ptr 表示共享所有权,适合对象会被多个模块或异步任务共同持有的场景,比如连接对象、任务上下文。weak_ptr 不增加引用计数,主要用来打破循环引用,或者在异步回调里判断对象是否还活着。
我一般不会默认使用 shared_ptr。如果一个对象的所有权非常明确,优先用 unique_ptr,这样代码更清楚,开销也更小。
代码:
#include <memory>
#include <vector>
using namespace std;
class RuleSet {
public:
vector<int> rules;
};
class Engine {
private:
unique_ptr<RuleSet> ruleSet_;
public:
Engine() : ruleSet_(make_unique<RuleSet>()) {}
};
6. 请说明智能指针在实际项目中的作用和使用场景
答案:智能指针在项目里的核心作用是把对象生命周期和资源释放管住,减少内存泄漏、重复释放和悬空指针。在云端安全策略编排与终端检测响应系统里,策略对象可能被多个工作线程读取,更新时会生成新版本策略,再通过原子替换或读写锁切换。旧策略如果还有线程在使用,就不能马上释放,这类场景可以用 shared_ptr<const RuleSet> 让读线程安全持有一份快照。
连接对象也适合用智能指针管理,因为连接关闭、读写回调、定时器回调可能同时涉及同一个对象。回调执行前拿一份 shared_ptr,可以避免处理过程中对象被提前析构。但对于内部独占资源,比如每个连接自己的输入缓冲区,用 unique_ptr 或直接作为成员对象更合适。
代码:
#include <memory>
#include <atomic>
using namespace std;
struct RuleSet {
int version;
};
class RuleManager {
private:
shared_ptr<const RuleSet> current_;
public:
shared_ptr<const RuleSet> getSnapshot() {
return atomic_load(¤t_);
}
void update(shared_ptr<const RuleSet> newRules) {
atomic_store(¤t_, newRules);
}
};
7. 你在项目中如何避免 shared_ptr 循环引用
答案:循环引用通常出现在两个对象互相持有对方的 shared_ptr。比如终端连接对象持有订阅任务,订阅任务又持有连接对象,如果两边都是 shared_ptr,即使外部已经不再使用,它们的引用计数也不会变成 0,最后导致内存泄漏。
解决方式是先明确所有权。谁真正拥有对象,谁用 shared_ptr;只是观察或者回调访问,就用 weak_ptr。在项目里,如果任务需要回调连接,我会让连接管理器持有连接的 shared_ptr,任务里只保存 weak_ptr,执行时通过 lock() 判断连接是否还存在。
代码:
#include <memory>
#include <iostream>
using namespace std;
class Connection {
public:
void sendResult() {
cout << "send result\n";
}
};
class Task {
private:
weak_ptr<Connection> conn_;
public:
explicit Task(weak_ptr<Connection> conn) : conn_(conn) {}
void finish() {
if (auto conn = conn_.lock()) {
conn->sendResult();
}
}
};
8. 在你的项目中,面向对象思想具体应用在哪些地方
答案:面向对象主要用在模块边界比较清晰、需要扩展的地方。比如安全事件处理可以抽象成统一的 EventHandler 接口,不同事件类型有不同处理器:进程启动事件、网络连接事件、文件变更事件、异常登录事件。调度器只负责根据事件类型找到处理器,不关心每个处理器内部怎么实现。
策略匹配模块也可以拆成规则接口,比如哈希匹配规则、正则匹配规则、阈值规则、时间窗口规则。这样后续新增一种规则时,不需要大改原来的调度逻辑。不过我也不会把所有代码都强行对象化。像协议解析、状态机推进、批量统计这种逻辑,用结构体加函数有时候更直接。
代码:
#include <memory>
#include <unordered_map>
using namespace std;
struct SecurityEvent {
int type;
int risk;
};
class EventHandler {
public:
virtual void handle(const SecurityEvent& event) = 0;
virtual ~EventHandler() = default;
};
class ProcessEventHandler : public EventHandler {
public:
void handle(const SecurityEvent& event) override {
// 处理进程事件
}
};
class Dispatcher {
private:
unordered_map<int, unique_ptr<EventHandler>> handlers_;
public:
void registerHandler(int type, unique_ptr<EventHandler> handler) {
handlers_[type] = move(handler);
}
void dispatch(const SecurityEvent& event) {
auto it = handlers_.find(event.type);
if (it != handlers_.end()) {
it->second->handle(event);
}
}
};
9. 面向对象的继承和多态特性在你的项目中有实际应用吗,请举例说明
答案:有,但不会把继承层次设计得太深。项目里比较适合继承和多态的是规则执行器。不同安全规则的匹配逻辑不一样,比如进程名黑名单、端口扫描检测、异常频率检测、文件路径匹配,它们都可以实现同一个 Rule 接口。策略引擎只持有 vector<unique_ptr<Rule>>,对每个事件依次调用 match(),这样新增规则类型时只需要新增派生类。
多态的好处是调度逻辑稳定,具体规则可扩展。缺点是虚函数调用有一点开销,而且如果规则数量非常多,逐条虚调用可能影响性能。实际优化时,可以把规则按事件类型、字段索引、风险等级预分组,减少无效匹配。
代码:
#include <string>
#include <vector>
#include <memory>
using namespace std;
struct Event {
string processName;
int port;
};
class Rule {
public:
virtu
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.
