C++ 기초플러스(15.3 예외처리) / C++로 Mariadb 연결 및 DB Table생성
#include <iostream>
#include <cstring>
#include <mariadb/conncpp.hpp>
void deleteTask(std::unique_ptr<sql::Connection> &conn, int id) {
try {
// Create a new PreparedStatement
std::unique_ptr<sql::PreparedStatement> stmnt(conn->prepareStatement("delete from tasks where id = ?"));
// Bind values to SQL statement
stmnt->setInt(1, id);
// Execute query
stmnt->executeQuery();
}
catch(sql::SQLException& e){
std::cerr << "Error deleting task: " << e.what() << std::endl;
}
}
// Update the completed value of a task record (indicated by id)
void updateTaskStatus(std::unique_ptr<sql::Connection> &conn, int id, bool completed) {
try {
// Create a new PreparedStatement
std::unique_ptr<sql::PreparedStatement> stmnt(conn->prepareStatement("update tasks set completed = ? where id = ?"));
// Bind values to SQL statement
stmnt->setBoolean(1, completed);
stmnt->setInt(2, id);
// Execute query
stmnt->executeQuery();
}
catch(sql::SQLException& e){
std::cerr << "Error updating task status: " << e.what() << std::endl;
}
}
// Create a new task record
void addTask(std::unique_ptr<sql::Connection> &conn, std::string description) {
try {
// Create a new PreparedStatement
std::unique_ptr<sql::PreparedStatement> stmnt(conn->prepareStatement("insert into tasks (description) values (?)"));
// Bind values to SQL statement
stmnt->setString(1, description);
// Execute query
stmnt->executeQuery();
}
catch(sql::SQLException& e){
std::cerr << "Error inserting new task: " << e.what() << std::endl;
}
}
// Print all records in tasks table
void showTasks(std::unique_ptr<sql::Connection> &conn) {
try {
// Create a new Statement
std::unique_ptr<sql::Statement> stmnt(conn->createStatement());
// Execute query
sql::ResultSet *res = stmnt->executeQuery("select * from tasks");
// Loop through and print results
while (res->next()) {
std::cout << "id = " << res->getInt(1);
std::cout << ", description = " << res->getString(2);
std::cout << ", completed = " << res->getBoolean(3) << "\n";
}
}
catch(sql::SQLException& e){
std::cerr << "Error selecting tasks: " << e.what() << std::endl;
}
}
// 반환값을 받아서 쿼리문으로 보내는데 받을때는 어떤형태로 받아오는지 확인해야한다. (sql::ResultSet *res 출력해서 클래스에 넣을 거냐 구조체에 넣을거냐 내가 보기 편하게 만들어야한다.)
// 출력을 하게 되는데 ./testdb2 showTasks명령어를 치면 select 로 db쿼리를 보내면 값이 출력된다.
// 터미널 컴파일 명령어 QT는 profile안에서 알아서 찾아서 해라 를 포함시켜놨다.
// profile안에 라이브러리 찾는 옵션 추가해주심 (.pro가 profile) 추가를 안하면 터미널 명령어가 길어짐
// mariadb https://mariadb.com/resources/blog/how-to-connect-c-programs-to-mariadb/ 코드 설명 보면 참고 할 수 있다. 번역을 돌려서 봐보자
// Main Process
int main(int argc, char **argv){
if (argc==1){
std::cout << "Please provide an argument.\n";
}
else {
try {
// Instantiate Driver
sql::Driver* driver = sql::mariadb::get_driver_instance();
// Configure Connection
sql::SQLString url("jdbc:mariadb://localhost:3306/todo");
sql::Properties properties({{"user","root"}, { "password","1234"}});
// Establish Connection
std::unique_ptr<sql::Connection> conn(driver->connect(url, properties));
// Use arguments to determine execution next steps
if (!strcmp(argv[1],"showTasks")) {
showTasks(conn);
}
else if (!strcmp(argv[1],"addTask")) {
if (argc != 3) {
std::cout << "Invalid arguments";
return 1;
}
addTask(conn, argv[2]);
}
else if (!strcmp(argv[1],"updateTaskStatus")) {
if (argc != 4) {
std::cout << "Invalid arguments";
return 1;
}
updateTaskStatus(conn, atoi(argv[2]), argv[3]);
}
else if (!strcmp(argv[1],"deleteTask")) {
if (argc != 3) {
std::cout << "Invalid arguments";
return 1;
}
deleteTask(conn, atoi(argv[2]));
}
// Close Connection
conn->close();
}
catch(sql::SQLException& e){
std::cerr << "Error Connecting to MariaDB Platform: " << e.what() << std::endl;
// Exit (Failed)
return 1;
}
}
// Exit (Success)
return 0;
}
void deleteTask(std::unique_ptr<sql::Connection> &conn, int id) {
try {
// Create a new PreparedStatement
std::unique_ptr<sql::PreparedStatement> stmnt(conn->prepareStatement("delete from tasks where id = ?"));
// Bind values to SQL statement
stmnt->setInt(1, id);
// Execute query
stmnt->executeQuery();
}
catch(sql::SQLException& e){
std::cerr << "Error deleting task: " << e.what() << std::endl;
}
}
=> 이함수는 주어진 ID의 작업을 삭제합니다.
=> 데이터 베이스 연결과 작업ID를 매개변수로 받습니다.
=> SQL 삭제 쿼리를 준비합니다.
=> 쿼리에 ID값을 바인딩합니다.
=> 쿼리를 실행 합니다.
=> 오류 발생 시 에러 메세지를 출력합니다. (try ~ catch ~ 로 예외처리)
void updateTaskStatus(std::unique_ptr<sql::Connection> &conn, int id, bool completed) {
try {
std::unique_ptr<sql::PreparedStatement> stmnt(conn->prepareStatement("update tasks set completed = ? where id = ?"));
stmnt->setBoolean(1, completed);
stmnt->setInt(2, id);
stmnt->executeQuery();
} catch(sql::SQLException& e) {
std::cerr << "Error updating task status: " << e.what() << std::endl;
}
}
=> 데이터베이스 연결, 작업 ID, 완료 상태를 매개변수로 받습니다.
=> SQL 업데이트 쿼리를 준비합니다.
=> 완료 상태와 ID 값을 쿼리에 바인딩합니다.
=> 쿼리를 실행합니다.
=> 오류 발생 시 에러 메시지를 출력합니다.
void addTask(std::unique_ptr<sql::Connection> &conn, std::string description) {
try {
std::unique_ptr<sql::PreparedStatement> stmnt(conn->prepareStatement("insert into tasks (description) values (?)"));
stmnt->setString(1, description);
stmnt->executeQuery();
} catch(sql::SQLException& e) {
std::cerr << "Error inserting new task: " << e.what() << std::endl;
}
}
=> 데이터베이스 연결과 작업 설명을 매개변수로 받습니다.
=> SQL 삽입 쿼리를 준비합니다.
=> 작업 설명을 쿼리에 바인딩합니다.
=> 쿼리를 실행합니다.
=> 오류 발생 시 에러 메시지를 출력합니다.
void showTasks(std::unique_ptr<sql::Connection> &conn) {
try {
std::unique_ptr<sql::Statement> stmnt(conn->createStatement());
sql::ResultSet *res = stmnt->executeQuery("select * from tasks");
while (res->next()) {
std::cout << "id = " << res->getInt(1);
std::cout << ", description = " << res->getString(2);
std::cout << ", completed = " << res->getBoolean(3) << "\n";
}
} catch(sql::SQLException& e) {
std::cerr << "Error selecting tasks: " << e.what() << std::endl;
}
}
=> 데이터베이스 연결을 매개변수로 받습니다.
=> SQL 선택 쿼리를 실행합니다.
=> 결과를 순회하며 각 작업의 ID, 설명, 완료 상태를 출력합니다.
=> 오류 발생 시 에러 메시지를 출력합니다.
// Main Process
int main(int argc, char **argv){
if (argc==1){
std::cout << "Please provide an argument.\n";
}
else {
try {
// Instantiate Driver
sql::Driver* driver = sql::mariadb::get_driver_instance();
// Configure Connection
sql::SQLString url("jdbc:mariadb://localhost:3306/todo");
sql::Properties properties({{"user","root"}, { "password","1234"}});
// Establish Connection
std::unique_ptr<sql::Connection> conn(driver->connect(url, properties));
// Use arguments to determine execution next steps
if (!strcmp(argv[1],"showTasks")) {
showTasks(conn);
}
else if (!strcmp(argv[1],"addTask")) {
if (argc != 3) {
std::cout << "Invalid arguments";
return 1;
}
addTask(conn, argv[2]);
}
else if (!strcmp(argv[1],"updateTaskStatus")) {
if (argc != 4) {
std::cout << "Invalid arguments";
return 1;
}
updateTaskStatus(conn, atoi(argv[2]), argv[3]);
}
else if (!strcmp(argv[1],"deleteTask")) {
if (argc != 3) {
std::cout << "Invalid arguments";
return 1;
}
deleteTask(conn, atoi(argv[2]));
}
// Close Connection
conn->close();
}
catch(sql::SQLException& e){
std::cerr << "Error Connecting to MariaDB Platform: " << e.what() << std::endl;
// Exit (Failed)
return 1;
}
}
// Exit (Success)
return 0;
}
이 함수는 사용자 입력에 따라 적절한 데이터베이스 작업을 수행하고, 예외 처리를 통해 오류 상황을 관리합니다.
=> 함수는 명령행 인자의 수(argc)와 인자 배열(argv)을 받습니다.
=> 인자가 없으면(argc==1) 사용법 메시지를 출력합니다.
=> 인자가 있는 경우, try-catch 블록 내에서 다음 작업을 수행합니다:
=> MariaDB 드라이버를 인스턴스화합니다.
=> 데이터베이스 연결 정보를 설정합니다(URL, 사용자 이름, 비밀번호).
=> 데이터베이스에 연결합니다.
=> 첫 번째 인자(argv1)에 따라 다른 작업을 수행합니다:
=> "showTasks": 모든 작업을 표시합니다.
=> "addTask": 새 작업을 추가합니다(인자가 충분한지 확인).
=> "updateTaskStatus": 작업 상태를 업데이트합니다(인자가 충분한지 확인).
=> "deleteTask": 작업을 삭제합니다(인자가 충분한지 확인).
=> 각 작업에 대해 해당 함수를 호출합니다.
=> 데이터베이스 연결을 닫습니다.
=> SQL 예외가 발생하면 에러 메시지를 출력하고 프로그램을 종료합니다.
=> 모든 작업이 성공적으로 완료되면 0을 반환하고 프로그램을 종료합니다.
** std::unique_ptr : 스마트 포인터 클래스로, 동적으로 할당된 객체에 대한 독점적 소유권 의미를 제공합니다.
- std::unique_ptr는 동적으로 할당된 객체를 소유하고 관리하며, unique_ptr의 범위를 벗어날 때 자동으로 삭제합니다.
- 소유한 객체가 적절히 할당 해제되도록 보장하여 메모리 누수를 방지합니다.
- 한 번에 하나의 unique_ptr만이 특정 객체를 소유할 수 있습니다.
- unique_ptr의 복사는 허용되지 않지만, 소유권 이전은 가능합니다.
#include <memory>
std::unique_ptr<int> ptr = std::make_unique<int>(42);
*ptr = 100; // 값 수정
int value = *ptr; // 값 접근
ptr.reset(); // 소유권 해제 및 관리되는 객체 삭제
std::unique_ptr는 C++에서 동적 메모리를 관리하는 강력한 도구로, 단일 소유권 시나리오에서 안전성과 효율성을 제공합니다.
#include <iostream>
#include <cstring>
#include <mariadb/conncpp.hpp>
// Delete a task record (indicated by id)
// 미리 문장을 준비시키고 (prepareStatement),?자리에 변수를 대입시킨다.
// executeQuery() 마리아디비 로 쿼리를 문자열로 보내실행 시킨다.
void deleteTask(std::unique_ptr<sql::Connection> &conn, int id) {
try {
// Create a new PreparedStatement
std::unique_ptr<sql::PreparedStatement> stmnt(conn->prepareStatement("delete from User where id = ?"));
// Bind values to SQL statement
stmnt->setInt(1, id);
// Execute query
stmnt->executeQuery();
}
catch(sql::SQLException& e){
std::cerr << "Error deleting task: " << e.what() << std::endl;
}
}
// Update the completed value of a task record (indicated by id)
void updateTaskStatus(std::unique_ptr<sql::Connection> &conn, std::string id, std::string pw) {
try {
// Create a new PreparedStatement
// ? 자리의 값은 매개변수로 받아옴
// setString(1, pw) : 첫번째 ?자리에 문자열 pw값을 넣겠다.
std::unique_ptr<sql::PreparedStatement> stmnt(conn->prepareStatement("update User set pw = ? where id = ?"));
// Bind values to SQL statement
stmnt->setString(1, pw);
stmnt->setString(2, id);
// Execute query
stmnt->executeQuery();
}
catch(sql::SQLException& e){
std::cerr << "Error updating task status: " << e.what() << std::endl;
}
}
// Create a new task record
void addTask(std::unique_ptr<sql::Connection> &conn, std::string id_insert, std::string pw_insert, std::string ph_insert) {
try {
std::cout << id_insert,pw_insert,ph_insert;
// Create a new PreparedStatement
std::unique_ptr<sql::PreparedStatement> stmnt(conn->prepareStatement("insert into User (id, pw, phone) values (?,?,?)"));
// Bind values to SQL statement
stmnt->setString(1, id_insert);
stmnt->setString(2, pw_insert);
stmnt->setString(3, ph_insert);
// Execute query
stmnt->executeQuery();
}
catch(sql::SQLException& e){
std::cerr << "Error inserting new task: " << e.what() << std::endl;
}
}
// Print all records in tasks table
void showTasks(std::unique_ptr<sql::Connection> &conn) {
try {
// Create a new Statement
std::unique_ptr<sql::Statement> stmnt(conn->createStatement());
// Execute query
sql::ResultSet *res = stmnt->executeQuery("select id,pw,phone from User");
// Loop through and print results
//쿼리 실행 끝나고 next()함수를 사용하여 한줄한줄 실행되고 넘어감
// while에 next()가 한줄 끝나고 다음줄의 내용이 없을때까지 값을 읽어온다
// select 쿼리문 사용 시 필요
while (res->next()) {
std::cout << "id = " << res->getString(1);
std::cout << ", pw = " << res->getString(2);
std::cout << ", phone = " << res->getString(3) << "\n";
}
}
catch(sql::SQLException& e){
std::cerr << "Error selecting tasks: " << e.what() << std::endl;
}
}
// 반환값을 받아서 쿼리문으로 보내는데 받을때는 어떤형태로 받아오는지 확인해야한다. (sql::ResultSet *res 출력해서 클래스에 넣을 거냐 구조체에 넣을거냐 내가 보기 편하게 만들어야한다.)
// 출력을 하게 되는데 ./testdb2 showTasks명령어를 치면 select 로 db쿼리를 보내면 값이 출력된다.
// 터미널 컴파일 명령어 QT는 profile안에서 알아서 찾아서 해라 를 포함시켜놨다.
// profile안에 라이브러리 찾는 옵션 추가해주심 (.pro가 profile) 추가를 안하면 터미널 명령어가 길어짐
// mariadb https://mariadb.com/resources/blog/how-to-connect-c-programs-to-mariadb/ 코드 설명 보면 참고 할 수 있다. 번역을 돌려서 봐보자
// Main Process
int main(int argc, char **argv){
if (argc==1){
std::cout << "Please provide an argument.\n";
}
else {
try {
// Instantiate Driver
//mariadb 드라이버를 가져와서 시작
sql::Driver* driver = sql::mariadb::get_driver_instance();
// Configure Connection
// (localhost부분)ip를 통해 외부에서 접속가능 / 포트는 마리아db접속 포트 / todo는 DB명
// SQLString : url를 통해
// Properties : DB 유저 아이디 와 비밀번호
sql::SQLString url("jdbc:mariadb://localhost:3306/todo");
sql::Properties properties({{"user","root"}, { "password","1234"}});
// Establish Connection
// url / properties 를 driver 안에 연결 매개변수 url과 properties를 넘긴다.
// 연결 요청을 보내는곳 (mariadb와)
//
std::unique_ptr<sql::Connection> conn(driver->connect(url, properties));
// Use arguments to determine execution next steps
if (!strcmp(argv[1],"showTasks")) {
showTasks(conn);
return 1;
}
else if (!strcmp(argv[1],"addTask")) {
if (argc != 5) {
std::cout << "Invalid arguments";
return 1;
}
addTask(conn, argv[2],argv[3],argv[4]);
}
else if (!strcmp(argv[1],"updateTaskStatus")) {
if (argc != 4) {
std::cout << "Invalid arguments";
return 1;
}
updateTaskStatus(conn, argv[2], argv[3]);
}
else if (!strcmp(argv[1],"deleteTask")) {
if (argc != 3) {
std::cout << "Invalid arguments";
return 1;
}
deleteTask(conn, atoi(argv[2]));
}
// Close Connection
conn->close();
}
catch(sql::SQLException& e){
std::cerr << "Error Connecting to MariaDB Platform: " << e.what() << std::endl;
// Exit (Failed)
return 1;
}
}
// Exit (Success)
return 0;
}
[오류 해결]
addTask() 함수에서 prepareStatement("insert into User (id, pw,phone) values(?)" 값을
prepareStatement("insert into User (id, pw, phone) values (?,?,?)" 로 변경
void addTask(std::unique_ptr<sql::Connection> &conn, std::string id_insert, std::string pw_insert, std::string ph_insert) {
try {
std::cout << id_insert,pw_insert,ph_insert;
// Create a new PreparedStatement
std::unique_ptr<sql::PreparedStatement> stmnt(conn->prepareStatement("insert into User (id, pw, phone) values (?,?,?)"));
// Bind values to SQL statement
// ? 자리의 값은 매개변수로 받아옴
// setString(1, id_insert) : 첫번째 ?자리에 문자열 id_insert값을 넣겠다.
stmnt->setString(1, id_insert);
stmnt->setString(2, pw_insert);
stmnt->setString(3, ph_insert);
// Execute query
stmnt->executeQuery();
}
catch(sql::SQLException& e){
std::cerr << "Error inserting new task: " << e.what() << std::endl;
}
}
15.3 예외
abort( ) 호출
● cstdlib(또는 stdilb.h) 헤더파일에 들어있는 함수 이다.
● 일반적으로, abort( )함수는 호출되었을 때 표준 에러 스트림(cerr가 사요하는 스트림)dp 'abnormal program temination"
(비정상적인 프로그램 종료)과 같은 메시지를 보내고 프로그램을 종료시키도록 구현되어 있다.
또한, abort( ) 함수는, 그 프로그램이 다른 프로그램에 의해 기동되었을 경우에, 그 프로그램을 가동 시킨 부모 프로세스나 운영체제에 컴파일러에 종속적인 어떤 값을 리턴한다.
● abort( )가 파일 버퍼(파일에 전송할 또는 파일로부터 전송받을 내용을 저장하는데 사용되는 메모리영역)를 비우는지 여부는 C++ 시스템마다 다르다.
** 원한다면 exit( )를 사용하여 메시지를 출력하지 않고, 파일 버퍼만 비울 수 있다.
//error1.cpp -- abort() 함수를 사용한다.
#include <iostream>
#include <cstdlib> // abort()를 사용하기 위한 헤더파일 호출
double hmean(double a, double b);
int main()
{
double x, y, z;
std::cout << "두 수를 입력하세요: ";
while (std::cin >> x >> y)
{
z = hmean(x,y);
std::cout << x << " , " << y
<< " 의 조화평균은 " << z << "입니다.\n" << std::endl;
std::cout << "다른 두 수를 입력하세요 <끝내려면 q>: ";
}
std::cout << "프로그램을 종료합니다.!\n";
return 0;
}
double hmean(double a, double b)
{
if (a == -b)
{
std::cout << "매개변수들을 hmean()에 전달할 수 없습니다.\n";
std::abort();
}
return 2.0 * a * b / (a + b);
}
에러 코드 리턴
리턴값이 성공이나 실패를 나타내도록 하는 예제
// error2.cpp -- 에러 코드를 리턴한다.
#include <iostream>
#include <cfloat> // (or float.h) DBL_MAX를 위해
bool hmean(double a, double b, double *ans);
int main()
{
double x, y, z;
std::cout << "두 수를 입력하세요: ";
while (std::cin >> x >> y)
{
if (hmean(x, y, &z))
std::cout << x << " , " << y
<< " 의 조화평균은 " << z << "입니다.\n"
<< std::endl;
else
std::cout << "서로 부정인 두 수의 조화평균은 구할 수 없다. \n";
std::cout << "다른 두 수를 입력하세요 <끝내려면 q>: ";
}
std::cout << "프로그램을 종료합니다!\n";
return 0;
}
bool hmean(double a, double b, double *ans)
{
if (a == -b)
{
*ans = DBL_MAX;
return false;
}
else
{
*ans = 2.0 * a * b / (a + b);
return true;
}
}
=> 터미널이 종료되지않고 다시 입력을 받아 실행 된다.
예외 메커니즘
● 프로그램 내에서 그 문제의 해결을 원하는 장소에서 예외 핸들러(Exception handler)를 사용하여 예외를 포착한다.
catch 키워드는 예외의 포착을 나타낸다.
● catch 키워드는, 예외가 발생했을 때 프로그램의 실행이 점프하는 지점을 나타내는 레이블의 역할을 한다. 예외 핸들러를 catch 블록이라고 한다.
● try 블록은 특별한 예외들이 발생할 수 있는 하나의 코드 블록이다. 그 뒤에는 하나 이상의 catch 블로들이 나온다.
// error3.cpp -- using an exception
#include <iostream>
double hmean(double a, double b);
int main()
{
double x, y, z;
std::cout << "두 수를 입력하세요: ";
while (std::cin >> x >> y)
{
try
{ // try 블록 시작
z = hmean(x, y);
} // try 블록의 끝
catch (const char *s) // 예외 핸들러의 끝
{
std::cout << s << std::endl;
std::cout << "두 수를 새로 입력하세요: ";
continue;
} // end of handler
std::cout << x << " , " << y
<< " 의 조화평균은 " << z << "입니다.\n"
<< std::endl;
std::cout << "다른 두 수를 입력하세요 <끝내려면 q>: ";
}
std::cout << "프로그램을 종료합니다!\n";
return 0;
}
double hmean(double a, double b)
{
if (a == -b)
throw "잘못된 humean() 매개변수: a = -b는 허용되지 않습니다.";
return 2.0 * a * b / (a + b);
}
적은 값이 double humean()로 가 a와 -b의 값을 비교하고 throw(예외가 발생했음을 알리는 문장의 구성에 사용된다.)를 발생하거나 return값을 반환한다.
▶▶▶ throw
throw는 예외가 발생했음을 알리는 문장의 구성에 사용된다.
throw expn;
위의 문장에서 expn은 변수, 상수 그리고 객체 등 표현 가능한 모든 데이터가 될 수 있으나, 예외상황에 대한 정보를 담은, 의미 있는 데이터이어야 한다. 그래서 expn의 위치에 오는 데이터를 가리켜 '예외'라고 표현한다.
또한 위의 문장이 실행되면 C++의 예외처리 메커니즘이 동작한다.
"throw에 의해 던져진 '예외' 데이터는, '예외' 데이터를 감싸는 try 블록에 의해서 감지가 되어 이어서 등장하는 catch 블록에 의해 처리된다."
try
{
if(예외가 발생한다면)
throw expn;
}
catch (type expn)
{
// 예외의 처리
}
* 다중 catch문의 주의점
catch문은 위에서부터 아래로 내려가면서 '예외' 데이터에 타당한 catch문을 찾는다.
근데 만약 유도 클래스의 자료형이 catch문 가장 위에 있고, 아래에 기초 클래스의 자료형이 밑에 있다고 가정하자.
그러면 기초클래스 자료형은 기초 클래스의 자료형인 catch문에 들어가지 않고 맨 위에 있는 유도 클래스의 자료형인 catch문에 들어가게 될 것이다.
그러니 상속 구조의 클래스 자료형을 둘 이상 catch문에 기입하게 되면 기초 -> 유도 순서대로 catch문을 기입하자.
class A
{ ... };
class B : public A
{ ... };
class C : public B
{ ... };
int main()
{
try
{ ... }
catch(C& expn)
{ ... }
catch(B& expn)
{ ... }
catch(A& expn)
{ ... }
}